De Voorhoede

front-end developers

Creating a streaming connection for text data, from the server to the client

Real-time communication with Server Sent Events

Sometimes in the land of web development, popularity trumps usefulness. This might be the case for websockets versus server-sent events. While socket.io became the go-to library for real time communication, Server-sent Events (SSE) is often ignored. Why? Perhaps developers are scared off by the vanilla-ness of SSE! The ease of implementation in both server and browser however, are its great benefits. Let’s dive in.

Why real-time communication

With real-time communication — instead of requiring the user to refresh their browser — new information can be appended to a web page instantly. Examples: an arrival & departure table for flights or trains. Remote health monitoring for elderly care. Visualising data like current traffic jams, or emerging topics on news feeds.

Traffic Jam
Real-time-communicate this! Photo credit: The History Channel

In early days, you’d have to resort to polling via AJAX. This put strain on both browser and server, and data was always at least seconds behind. Since, new technologies have emerged that push information to the browser and back to the server as soon as it arrives: WebRTC, Websockets, and Server-sent Events.

WebRTC, Websockets and SSE

WebRTC and Websockets both support binary streams, and are bidirectional, while SSE is text-only and unidirectional. The most important difference between WebRTC and Websockets is that the former is built for peer-to-peer high quality audio/video streaming, while the latter is primarily aimed at client/server communication.

To keep things simple, we’ll take WebRTC out of the equation.

Server-sent Events are used to create a persistent unidirectional streaming method from the server to the client. Two main benefits over Websockets: a broken SSE connection will automatically try to re-establish itself, and SSE are sent over good old HTTP. This makes it more scalable and easier to implement. The best part is: you won’t need an extra framework or npm package to set up an SSE stream.

Server set-up

Let’s get to work. Here is a simple example on a Node.js+Express server set-up. All you need is the Express framework:

const express = require('express');
const app = express();

app.get('/eventstream', (req, res, next) => {
    res.set({
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    });
    app.on('message', data => {
        res.write(`event: message\n`);
        res.write(`data: ${JSON.stringify(data)}\n\n`);
    });
});

As you can see, the eventstream route creates a response with some headers, the most important of which is the Content-Type: text/event-stream header. This makes sure the response is a continuous stream of text.

Then, by using Express’s built-in event emitter, we listen to the message event and write to the response stream. SSE expects the text to send to be in a specific format. That explains the newline characters; the event and data keywords are part of SSE specifications, too.

Let’s say we want to send some data when a message is posted to the server:

app.post('/message', (req, res, next) => {
    const message = req.body.message;
    // ...
    // Some code here to handle the message, 
        // by saving it in a database for instance
    // ...
    app.emit('message', { 
        title: 'New message!',
                message,
                timestamp: new Date() 
        });
})

That’s it! Whenever a message is posted to the /message endpoint, an event will be fired that writes to the event stream. Now all we need to do is pick it up on the client side.

Client set-up

On the client side, we listen to the event stream. This is done with an EventSource instance (don’t forget to polyfill for IE/Edge!).

const evtSource = new EventSource('/eventstream');
evtSource.addEventListener('message', event => {
    const data = JSON.parse(event.data);
        //
        // ...insert magic here!
        //
})

The EventSource instance opens a connection to the endpoint on our server. Whenever new text is added to the event stream, the event listener fires. We parse data from the event object, and from there your app logic takes over. It’s that easy.

So what do you think? Next time you need realtime stuff, why not SSE!

Coda

How about the Push API, you ask? The bottom line with the Push API is that it needs a service worker. Then again, when you’re building a PWA, there is a lot of power in push messages. They are picked up even if the browser app is closed.

You want SSE, but need binary data? Let’s say you want to send an image: you could use base64 encoding. There is a lot of overhead in bytesize (about a third) though. An alternative would be to send the URI of the image to the client, and fetch it asynchronously. An added benefit: the fetched asset could leverage caching and compression, which a streamed asset can’t.