Blog

Real-time communication with Server Sent Events

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

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.

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`);
	});
});

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 on an intersection
Real-time-communicate this!

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 messageevent 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.

← All blog posts

Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

Join our team