Server-Sent Events With Node
Tags: javascript, node, express
Published: 2018-08-17 11:53:33

---
title: Server-Sent Events With Node
published: true
tags: JavaScript,Node,Express
canonical_url: https://jasonbutz.info/2018/08/server-sent-events-with-node?utm_source=dev.to&utm_content=server-sent-events-with-node
---

Server-Sent Events (SSEs) allow one-way communication from the server to the client. They can be very useful for things like notifications or activity feeds. I've recently used them in a project to display output from a background process in the browser.

In the browser, you connect to the server using the [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) interface and just add event listeners. It is really very easy.

```javascript
const sseSource = new EventSource('/event-stream');
sseSource.addEventListener('message', (e) => {
const messageData = e.data;
// ...
// ...
});
// When finished with the source close the connection
sseSource.close();
```

Things on the server-side are a bit more complicated, but not by a lot. There are just very specific things that need to be done.
The HTTP connection needs to be kept open. I saw a lot of examples that included `req.socket.setTimeout(Infinity)` but that isn't necessary and throws an error at least in Node v8 and above. By default, the connection is kept open on the Node end. You should send a `Connection: keep-alive` header to ensure the client keeps the connection open as well. A `Cache-Control` header should be sent with the value `no-cache` to discourage the data being cached. Finally, the `Content-Type` needs to be set to `text/event-stream`.

With all of that done a newline (`\n`) should be sent to the client and then the events can be sent. Events must be sent as strings, but what is in that string doesn't matter. JSON strings are perfectly fine.
Event data must be sent in the format `data: <DATA TO SEND HERE>\n`. The `data:` portion is important because you can provide IDs and types for the events. An example with both might look like this:

```text
id: 42
event: deepThoughtOutput
data: I have finished computing the answer
```

It's important to note that at the end of each line should be a newline character. To signify the end of an event an extra newline character needs to be added as well. For the example above the `EventSource` listener should be attached to a `deepThoughtOutput` event instead of the `message` event.
Multiple data lines are perfectly fine, the one below works without an issue.

```text
data: [
data: "Array Element 1",
data: "Array Element 2",
data: ]
```

When IDs are being used there is the `Last-Event-ID` HTTP header that you may run into. If the connection is broken the client will send the last ID it received in the `Last-Event-ID` header to allow the events to be resumed from where they were left off. Pay special attention to any polyfill libraries you use in this area. Some use a query string instead of a header.

Below is an example of a node application that uses SSEs.

```javascript
const express = require('express');
const app = express();
function sseDemo(req, res) {
let messageId = 0;
const intervalId = setInterval(() => {
res.write(`id: ${messageId}\n`);
res.write(`data: Test Message -- ${Date.now()}\n\n`);
messageId += 1;
}, 1000);
req.on('close', () => {
clearInterval(intervalId);
});
}
app.get('/event-stream', (req, res) => {
// SSE Setup
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res.write('\n');
sseDemo(req, res);
});
app.listen(3000);
```

Note the `close` event handler for the request. If you need to clean anything up this is the place to do it. In this example, I stop the interval timer so that it doesn't continue running needlessly on the server.

When I was implementing SSEs I was doing so on top of an application template someone else had built and not fresh from an example. I was running into an odd issue where my events would only be received by the client after the connection closed. After some digging, I found it was due to the compression setup.

The [`compression` npm package](https://www.npmjs.com/package/compression) looks at mime-types to determine if the response should be compressed. All `text` mime-types are compressed by default which means the events being sent were being buffered for compression. I was never getting enough into the buffer for it to be flushed. If you run into this you have a few options. You can disable compression for `text/event-stream` mime-types, disable compression for your SSE endpoint, or you can call `req.flush()` after every event to flush the buffer and send your event to the client.

Another important bit of information is authentication. There is no ability to send custom headers through `EventSource`. If you need to pass a token to the server you should use cookies. In my case, I set an `HttpOnly` cookie on the server when the user authenticates and then use that to verify their identity for events.

If your client and server are not at the same origin there are some CORS considerations you will need to make that I don't cover here. If you use a load balancer or proxy you will also need to make sure it won't close the connection early. The client would try and reconnect, but it would add extra overhead to frequently reconnect.

Below are some additional resources if you want to look into Server-Sent Event in more detail.

[Mozilla Developer Network - Using server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
[WHATWG Server-Sent Events Living Standard](https://html.spec.whatwg.org/multipage/server-sent-events.html)
[HTML5 Rocks EventSource tutorial](https://www.html5rocks.com/en/tutorials/eventsource/basics/)
[Server-Sent Events with Node.js (Express) tutorial](https://tomkersten.com/articles/server-sent-events-with-node/)