Express Server-Sent Events Middleware

raw JSON →
1.0.0 verified Thu Apr 23 auth: no javascript

express-sse (from `dpskvn/express-sse`) is a lightweight Express.js middleware designed for easily implementing Server-Sent Events (SSE) in Node.js applications. It provides a straightforward API to send real-time, unidirectional updates from the server to connected clients over a standard HTTP connection, making it suitable for dashboards, live feeds, and notification systems. The current stable version is `1.0.0`, released on April 17, 2025, indicating active development and maintenance. Its primary differentiator is its simplicity and focus on one-way communication, which often makes it a more suitable and simpler alternative to WebSockets for server-to-client data streaming scenarios. It handles the necessary HTTP headers and connection management for SSE, allowing developers to focus on sending data without extensive boilerplate.

error Client not receiving messages or receives all messages at once when `res.end()` is called.
cause The SSE stream is being buffered by a proxy, compression middleware, or Express itself is not flushing headers correctly.
fix
Ensure res.flushHeaders() is called after setting SSE-specific headers (handled by express-sse.init). Check for proxy buffering or compression middleware interfering with the stream and disable them for the SSE route. For express-sse, ensure sse.init is correctly applied to the route.
error SyntaxError: Unexpected token 'export'
cause Attempting to use ES module `import` syntax in a CommonJS Node.js project or environment without proper transpilation/configuration.
fix
If your project uses CommonJS (no "type": "module" in package.json), use const SSE = require('express-sse');. If using ES modules, ensure your Node.js environment supports them (Node.js >= 12 with "type": "module" or via Babel/TypeScript compilation), and use import SSE from 'express-sse';.
error TypeError: Cannot read properties of undefined (reading 'init')
cause The `SSE` constructor was not properly imported or initialized, or `express-sse` might not be the package being installed.
fix
Verify that express-sse is correctly installed (npm install express-sse) and that the SSE class is being imported/required as const SSE = require('express-sse'); or import SSE from 'express-sse'; before attempting to call new SSE().
gotcha When deploying `express-sse` (or any SSE solution) behind reverse proxies (like Nginx, Apache) or load balancers, ensure that the proxy is configured to not buffer responses. Proxies often buffer entire responses before sending them to the client, which can prevent SSE streams from delivering real-time updates. Look for settings like `proxy_buffering off` in Nginx.
fix Configure your reverse proxy/load balancer with 'no-buffering' settings for SSE endpoints. For Nginx, add `proxy_buffering off; proxy_cache off; proxy_http_version 1.1; proxy_set_header Connection '';` to your location block.
gotcha Using compression middleware (e.g., `compression` package) with SSE routes can break the event stream, as the middleware will try to compress the response, effectively buffering it until completion. This defeats the purpose of real-time streaming.
fix Exclude SSE routes from compression middleware. For example, if using `compression`, apply it conditionally: `app.use(compression({ filter: shouldCompress }))` and define `shouldCompress` to return `false` for your SSE paths.
gotcha Browsers enforce a limit on the number of concurrent HTTP connections to a single domain (typically 6 per browser, not per tab). If multiple SSE tabs are open or other long-polling requests are made, new SSE connections might be blocked.
fix Be mindful of client-side connection limits. Consider consolidating data streams where possible, using a single SSE connection for multiple types of events, or leveraging HTTP/2 which allows multiplexing over a single TCP connection. For applications requiring many simultaneous connections, WebSockets might be a better fit.
gotcha Newlines within `data` fields in SSE messages can cause unexpected truncation or parsing errors on the client side, as `\n` signifies the end of a line in the SSE protocol, and `\n\n` signifies the end of an event.
fix When sending multi-line content or data that might contain newlines (e.g., JSON strings, markdown), ensure that each line in your data is prefixed with `data:` and ends with `\n`, with the final data line followed by two `\n\n` to properly terminate the event. Alternatively, JSON-serialize your data and then stringify it, ensuring any internal newlines are escaped as `\n` within the JSON string itself (e.g., `data: {"text": "line1\nline2"}\n\n`).
npm install express-sse
yarn add express-sse
pnpm add express-sse

This example demonstrates how to set up an Express.js server using `express-sse` to stream real-time updates. It initializes SSE with some optional initial data, then sends periodic 'time-update' events. It also shows how to send custom-named events (like 'error') and includes client-side JavaScript for consuming the stream using the browser's `EventSource` API.

import express from 'express';
import SSE from 'express-sse';

const app = express();
const port = 3000;

// Initialize SSE with optional initial data and options
const sse = new SSE(["Initial message 1", "Initial message 2"], {
  isSerialized: true, // Send initial data as separate events
  initialEvent: 'initial-data' // Optional event name for initial data
});

// Set up the SSE endpoint
app.get('/stream', sse.init);

// Send events periodically
let counter = 0;
setInterval(() => {
  const message = `Server time: ${new Date().toLocaleTimeString()} - Count: ${counter++}`;
  sse.send(message, 'time-update', Date.now()); // content, eventName, customID
  
  // Example of sending an error event
  if (counter % 5 === 0) {
    sse.send({ error: 'Heartbeat error detected' }, 'error', `error-${Date.now()}`);
  }

}, 2000);

// Update initial data dynamically
app.post('/update-init', (req, res) => {
  const newInitialData = req.body?.data || ["Updated initial content"];
  sse.updateInit(newInitialData);
  res.status(200).send('Initial data updated');
});

app.listen(port, () => {
  console.log(`Express SSE server listening at http://localhost:${port}`);
  console.log(`Connect to SSE stream at http://localhost:${port}/stream`);
});

/*
// Client-side JavaScript (e.g., in an HTML file):
// Ensure to include express and body-parser for the server, and a client-side HTML file to test.
// Example client.html:
// <script>
//   const eventSource = new EventSource('/stream');
//
//   eventSource.onopen = () => console.log('SSE connection opened.');
//   eventSource.onerror = (error) => {
//     console.error('SSE Error:', error);
//     eventSource.close();
//   };
//   eventSource.onmessage = (event) => {
//     console.log('Received generic message:', event.data);
//   };
//   eventSource.addEventListener('time-update', (event) => {
//     console.log('Received time-update:', event.data, 'ID:', event.lastEventId);
//   });
//   eventSource.addEventListener('initial-data', (event) => {
//     console.log('Received initial-data:', event.data);
//   });
//   eventSource.addEventListener('error', (event) => {
//     console.error('Received error event:', event.data);
//   });
// </script>
*/