Push instant updates with EventSource API

EventSource is a handy API supported by all browsers, which runs over the regular HTTP protocol and doesn’t require setting up a separate WebSocket service. However, it has limitations, such as data being one-directional (server to client) or a connection being constantly occupied. Let’s dive in.

The server

We will start with setting up a simple server using one of the ways from the previous article. After you have the server, we will set up a separate route to manage the Event stream. Event stream is a simple stream of UTF-8 encoded text data, so we don’t need any external packages or special tools to output it. To keep it simple, we will use Express to handle all the routing. So, install it with npm i express and create a main.js file for our server.

const fs = require('fs')
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/html'
    })
    res.write(fs.readFileSync('home.html'))
    res.end();
});
app.get('/sse', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache'
    })
    let interval = setInterval(() => {
        res.write(`event: date\n`)
        res.write(`data: ${new Date().toUTCString()}\n\n`)
        if (Math.random() < 0.1) {
            console.log('Got random < 0.1, closing connection')
            clearInterval(interval)
            res.end()
        }
    }, 1000)
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

In this example, we created two routes, one for the home template and another for the server-side event stream. The sse route needs to declare the special content type text/event-stream and tell the browser not to cache anything.

To simulate event stream, we are going to run the same function with a 1s interval, all until randomly Math.random() falls before 0.1 and then the connection is closed with res.end(). Now that we have everything ready on the server, it’s time to work on our example home.html.

The client

Create a simple HTML document with the following (or similar) structure:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>EventSource Example | Code with Node.js</title>
</head>
<body>
        
    <ul id="messages"></ul>

</body>
</html>

With this in place, we can create our EventSource element. The class’s constructor accepts a single URL argument and an optional list of options that accepts a withCredentials boolean only.

We will add two basic event handlers for errors and our date messages that we are generating on the server.

const es = new EventSource('/sse');
es.addEventListener('error', function(err) {
    // whenever you get an error, most likely it got disconnected, you can check readyState
    console.log(es.readyState);
    console.error(err);
});
es.addEventListener('date', function(msg) {
    let li = document.createElement('li');
    li.innerHTML = msg.data;
    document.querySelector('#messages').appendChild(li);
});

This is enough to get it working. After closing the connection on the server, the client will automatically attempt to reconnect to the server. The way the browser decides when to reconnect is currently not configurable at the time of writing. However, some alternative libraries exist that mimic the functionality of EventSource by providing additional abstraction layers on top of fetch(), like Microsoft’s fetch-event-source. You might want to look at those to have better control over what’s happening (the link is at the end of this article).

Back to our source code… When we get the date event, we create a simple list item and dump the data. You could also pass the structured data on the server by encoding it with JSON.stringify(), and then decoding it on the client with JSON.parse(msg.data).

When you want to stop the EventSource, use the .close() method to stop any activity and cancel reconnections.

Resources


Did you know that I made an in-browser image converter, where you can convert your images without uploading them anywhere? Try it out and let me know what you think. It's free.

Leave a Comment

Your email address will not be published. Required fields are marked *