Server-Sent Events with Node.js
Server-sent events (SSE) allows clients to receive updates from a server over a single HTTP connection without having to poll the server for updates. This makes it a great fit for applications that need to display real-time data such as stock tickers, news feeds, or social media updates.
SSE vs WebSockets
You might be wondering how SSE differs from WebSockets. The primary difference is that WebSockets are a two-way communication protocol that allows clients to both send and receive data to and from the server. SSE, on the other hand, is a one-way communication protocol that only allows the server to send messages to the client.
For example, you might use WebSockets to build a chat application where clients can send messages to each other. Whereas you might use SSE to build an activity feed for your application where the server sends updates to the client.
Creating an SSE endpoint
First things first, we'll need to create an endpoint that streams events to the client. For this, we'll be using endpts, a platform that makes it easy to build and deploy serverless APIs with TypeScript.
The complete code for this example can be found in the endpts-samples/server-sent-events repository.
Let's define a route that will allow a client to subscribe to the SSE endpoint by sending a GET
request to /events
:
// routes/events.ts
import type { Route } from '@endpts/types'
export default {
method: 'GET',
path: '/events',
async handler() {
let eventCount = 0
// create a stream that emits events every 500ms to
// simulate some real-time data source
const stream = new ReadableStream({
start(controller) {
controller.enqueue('data: stream started\n\n')
},
async pull(controller) {
controller.enqueue(`id: ${eventCount}\n`)
controller.enqueue(`data: event #${eventCount}\n\n`)
eventCount++
return new Promise((r) => setTimeout(r, 500))
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
},
} satisfies Route
In the code above, we create a ReadableStream that emits events every 500ms. We then return a Response object with the stream as the body and the appropriate headers for SSE.
Since endpts Serverless Functions support streaming out-of-the box, the data will sent to the client as soon as it's available instead of buffering it in memory.
Subscribing to the SSE endpoint
Next, let's create the client-side code that subscribes to the SSE endpoint we just created.
// routes/index.ts
import type { Route } from '@endpts/types'
// HTML page to render the server-sent events
const pageHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>server-sent events - endpts</title>
</head>
<body>
<h1>Server-sent events</h1>
<button onclick="source.close()">Stop event stream</button>
<ul id="events"></ul>
<script>
const events = document.getElementById("events");
const source = new EventSource("/events");
source.onmessage = (event) => {
events.innerHTML += "<li>" + event.data + "</li>";
};
</script>
</body>
</html>
`
export default {
method: 'GET',
path: '/',
async handler() {
return new Response(pageHtml, {
headers: {
'Content-Type': 'text/html',
},
})
},
} satisfies Route
In the route above, we return an HTML page that subscribes to the SSE endpoint we created earlier.
The JavaScript snippet creates a new EventSource object that connects to the /events
endpoint and listens for events, which we then append to the events
list element to display on the page:
const events = document.getElementById('events')
const source = new EventSource('/events')
source.onmessage = (event) => {
events.innerHTML += '<li>' + event.data + '</li>'
}
The great thing about SSE is that the browser does most of the heavy lifting for us. Once the browser executes the snippet above, it will automatically open a connection to the /events
endpoint and start receiving events from the server. Additionally, if the connection is lost, the browser will automatically try to reconnect to the endpoint, passing the last event ID received in the Last-Event-ID
header.
Check out the MDN docs for a complete reference on the EventSource API and the different events it supports.
Testing our SSE endpoint
If you have the endpts-samples/server-sent-events repository cloned locally, you can run the following command to install the dependencies and start the development server (make sure you're using Node.js v18 or higher):
npm install && npm run dev

Then, we can open up our browser to http://localhost:3000 to see the events being streamed to the client:

Tip: you can use the Chrome devtools EventStream tab to inspect the events being streamed to the client in real-time:

Deploying our application
Now that we have our application working locally, let's deploy it to endpts. Head over to the endpts dashboard and create a new project. For the Source URL, you can use the sample repository: https://github.com/endpts-samples/server-sent-events
:

Hit the Deploy button. Once the project has been deployed successfully, you can grab the deployment URL and open it in your browser to see the events being streamed to the client:
