Introduction to SSE and difference from WebSockets
Hey, how's it going? Today we're going to talk about Server-Sent Events, which is one of the techniques you can use to automatically push data to your client from your server in real time.
Now, in a previous video that I made I talked about WebSockets, which is sort of the same thing. But a key distinction with WebSockets and Server-Sent Events is that WebSockets is a bi-directional communication, meaning that both the server can push data and the client can also push data back to the server.
Whereas Server-Sent Events, as the name implies, is unidirectional — it's only for situations where you only want the data coming from the server and you never have data coming back from the client. So in this video we're going to do a super quick tutorial on how to get up and running with Server-Sent Events using NestJS and then we're also going to take a look at the EventSource API, which is the interface that you're going to use on the client side to basically subscribe to events from the server.
Scaffolding a new NestJS application
All right, so I have a terminal here open to my desktop and I'm just going to use the NestJS CLI to create a new application. So I'm going to do: nest new sse-demo — so this is the name of my project. I'll hit enter here, it'll install.
All right, after that completes we'll open up the code in VS Code and we'll go into app.controller.ts, and this is where we're going to start adding some stuff, right?
All right, so what we're going to do here first is we're going to create a new method. Let's call it sendEvent, and then we're going to add a decorator here using Sse, and then we have to provide a string in here which, as you can see in the documentation here, is the path. So let's just call this event. So structurally it's very similar to just like any other controller route, right, except that instead of you specifying the HTTP method you specify the Sse decorator with a path. Similarly, this also takes in a path. All right, so let's delete this so that we can focus on the Sse event here.
Defining the Observable return shape and RxJS imports
Now one of the things that NestJS requires for SSE events is that you're returning an Observable, and as you can see in NestJS it will use RxJS Observables which is already installed — it's one of the dependencies that NestJS uses. And then we have to specify the shape of the data that we're going to return to the client, so let's define real quick an interface and let's call this MessageEvent, and let's just say that there's data in here that is a string, or perhaps it could also be an object, right.
So we'll take this MessageEvent and we'll put it in here which basically says that our SSE method here returns something of this shape. Right, so we need to return something that has data in it and some string.
Creating an interval Observable and mapping values
Now there's a missing piece in here because it can't just be this event — it needs to be an Observable — so we're going to pull a couple of things from RxJS to pull this off. You don't need to really be that aware of what RxJS is or even what an Observable is; that's something that I can probably cover at another video — let me know if you're interested in that — but anyways, let me write a few lines of code here and I'll explain what it does.
So I'm going to pull an interval from RxJS and I'm going to pass in 1000 in here. Now what this does is basically it's gonna on an interval return a number, right, and we're saying every second — so this is a thousand milliseconds — it's gonna return a number. So you can imagine that at every second it's gonna go zero, one, two, and it'll keep doing that every second.
And what we're going to do with that is we'll take each number, we'll pipe it, and then we'll basically map the number to some kind of string or object, right? So we'll do map, and again these are all coming from RxJS so make sure to import map from RxJS. So again, remember this is returning a number every second, so this is actually taking in a value — let's call num which is a number — and we're just going to map that to a string. Let's do: 'hello ' + num. Right, so what we expect to happen here is again every second it's gonna return a number and then we're mapping that to a string that just says 'hello' and that number. So you can imagine that every second it's gonna do 'hello 0', 'hello 1', 'hello 2' and so on. Right, like it just keeps going. So basically we're just going to keep pinging whoever the client is that is connected to us and we're going to keep saying 'hello 0, 1, 2' and so on.
Recap of SSE server basics and creating a static client
All right, and actually that's the basics of what you need to know in terms of creating a Server-Sent Event: you just need to create a method in a controller and you need to return an Observable of some kind of shape. Right.
And now we're going to create a client that we're going to use to test this out. So what I'm going to do is I'm going to create a new folder — I'm going to call this client — and within that folder I'm going to create an index.html. Right, and within this index.html we're just going to create a script, right, and this is where we're gonna connect to our server in a little bit here.
But first, before we do that, what we're gonna do is we need a way to actually serve up this index.html. So an easy way to do that is using the serve-static module, so we're going to do: npm install --save @nestjs/serve-static. Once you have that installed we're gonna go to app.module and then we're just gonna add an import in here from serve-static and we're gonna say that we're gonna serve up whatever is in our client folder, which is just our index.html.
Now the catch is it's gonna do this at the base route of our server, so we need to change our app controller here to have its own API base path so that it doesn't conflict with that. So we're just going to add 'api' here and this will make sense in a little bit.
Using EventSource in the browser and running the app
All right, so now in our index.html what we're going to do is, as I mentioned earlier, there is the EventSource API which is the API that you can use to basically subscribe to Server-Sent Events, and it's actually pretty simple. We're just going to do: const eventSource = new EventSource(), and all you need to pass in here is the path to the thing that you're subscribing to, right? So we said that we have a base path of '/api' and then if we go back to our app controller we see that we also have a path here of 'event', so what we need is '/api/event'.
And then this pretty much makes a persistent connection to the server and we can just subscribe to the event 'onmessage'. So we're going to do eventSource.onmessage. Now there's a payload that comes in here and then within the method is where we actually handle it. So this payload is actually the same shape as this interface that we had on the server side, the MessageEvent, so we can just destructure the data here. And what we'll do with this is just that every time a message comes in we're going to create a new li HTML element and then we'll just add the data in there as the text of it. So we'll do message.innerText = data, right, so we expect data here to be what we're returning here which is gonna be, you know, 'hello' and then the number every one second. And then as we create these elements and we fill in the text of it we're just going to add that to the document body.
All right, and then at this point we can probably run our application and see everything running. Let's do npm run start:dev. Now when I go to localhost:3000, what that's going to do is it's going to make that connection, right? It's going to do EventSource on '/api/event' and then as you can see we're automatically subscribed and we're adding a new message at each push, right? And let me refresh just so you can see again. Cool — so we're basically done with the tutorial at this point. So there's really not much more to learn.
Connection limit caveat and final thoughts
There is one gotcha that you need to understand though. So you'll see on the EventSource documentation and MDN they have a warning in here that says, if you're not using HTTP/2, one of the problems with SSE is that there's a max of six connections. So let me kind of show what the problem is. So right now we have effectively one connection, so I'm going to open up six tabs to localhost:3000. So as you can see right now I have six tabs open on localhost:3000 and they're all subscribing to the events. Notice what happens when I open up another one: you can see that here it just spins like it fails to connect because once you reach the six connections it basically kind of stops from there.
So if you're trying to compare this with WebSockets that's probably one of your major considerations — can you use HTTP/2 with your application? Now one of the benefits that I see comparing with WebSockets is that it's just dead simple, right? It really didn't take us that much time to get up and running and it's very simple to connect and close. There's pretty good documentation here and again the only catch is really if you can't use HTTP/2 then there's a very low number of connections that you can make.
All right guys, that's it for me today. Super quick tutorial, hopefully it was interesting to you. Let me know in the comments if you have any thoughts or feedback.