With Server-sent events you are able to send one-directional events to a web page.
Here is an example of how I used this functionality in Minimal Analytics
Context
In Minimal Analytics I needed to send updates about live users active on this blog back to the dashboard available at s.cri.dev
Here is how it looks
This is a perfect use case for Server-sent Events.
Below you’ll find example code to implement it yourself in a bare Node.js based HTTP server.
All the code is available on in the Minimal Analytics Github repository
On the client side
I want to start with the client to better explain what happens under the hood.
As per MDN documentation
An EventSource instance opens a persistent connection to an HTTP server, which sends events in text/event-stream format
On the client/dashboard side, the code to start a one-directional communication channel to the server looks like this:
const eventSource = new window.EventSource('/')
eventSource.onmessage = (message) => {
if (!message || !message.data) return console.error('skipping empty message')
const live = JSON.parse(message.data, {})
console.log('sse live visitors', live)
// in a react component you could update the state
this.setState({ live })
}
This way I’m connecting the web page through a SSE channel and listening for new messages.
Remember: only the server can send messages downstream to the client, it’s a one-directional channel (in contrast to WebSockets)
If you open the network tab in your browser, you can inspect the messages that are sent down the wire:
On the server side
In a bare Node.js HTTP server, you could integrate the SSE channel like this:
const connections = []
const server = http.createServer(function (req, res) {
...
if (req.headers.accept && req.headers.accept.includes('text/event-stream')) {
handleSSE(res, connections)
return sendSSE(JSON.stringify(live), [res])
}
...
})
connections
represents all active SSE connections.
I’m setting up a condition to check if the request’s Accept header is an Event Stream.
If so, handleSSE
takes care of adding this new request to the list of active connections and replying with the current live visitors with the sendSSE
function.
handleSSE
handleSSE
adds the response stream to the active connections array and removes it when the connections is closed.
Additionally it replies to the browser with the correct headers and status code.
Note the text/event-stream
Content-Type
header.
function handleSSE (res, connections = []) {
connections.push(res)
res.on('close', () => {
connections.splice(connections.findIndex(c => res === c), 1)
})
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
})
}
sendSSE
sendSSE
simply loops over all active connections and broadcasts a message.
Messages consist of an id
line (simply the current date could suffice) and a data
line.
function sendSSE (data, connections = []) {
connections.forEach(connection => {
const id = new Date().toISOString()
connection.write('id: ' + id + '\n')
connection.write('data: ' + data + '\n\n')
})
}
Real-world example
You can find all the code used in this post in the Minimal Analytics git repository