Sse Express
title: "SSE with Express.js" description: "Learn how to implement Server-Sent Events in Express.js applications for real-time data streaming with route-specific event handlers." weight: 15 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "sse"]
Express.js makes it easy to add SSE endpoints alongside REST APIs. Using standard HTTP response objects, you can stream events to clients without additional dependencies.
## What You'll Learn
- Setting up SSE routes in Express
- Managing connected clients
- Broadcasting events to multiple clients
- SSE with authentication middleware
- Heartbeat and keepalive
## Why It Matters
Express is the most popular Node.js web framework. Adding SSE to existing Express applications enables real-time features without changing your architecture.
## Real-World Use
A SaaS application adds SSE endpoints to its Express API for real-time notifications. Users receive billing alerts, team activity updates, and system notifications without any additional infrastructure.
## Flow Chart
```mermaid
flowchart LR
A[Express Server] --> B[REST API Routes]
A --> C[SSE Route: /events]
C --> D{Client Manager}
D --> E[Client 1]
D --> F[Client 2]
D --> G[Client N]
H[Application Events] --> D
Code Examples
Example 1: Basic SSE Route in Express
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
// Send initial connected event
res.write(`event: connected\ndata: {"status": "connected"}\n\n`);
// Send periodic updates
const intervalId = setInterval(() => {
const data = JSON.stringify({
time: new Date().toISOString(),
random: Math.random(),
});
res.write(`data: ${data}\n\n`);
}, 5000);
// Cleanup on disconnect
req.on('close', () => {
clearInterval(intervalId);
console.log('Client disconnected');
});
});
app.listen(3000);
Expected output: Express route serves SSE at /events, sending updates every 5 seconds and cleaning up on disconnect.
Example 2: Client Manager for Broadcasting
const express = require('express');
const app = express();
class SSEClientManager {
constructor() {
this.clients = new Map();
this.clientIdCounter = 0;
}
addClient(res) {
const id = ++this.clientIdCounter;
this.clients.set(id, res);
res.on('close', () => {
this.clients.delete(id);
console.log(`Client ${id} disconnected. Total: ${this.clients.size}`);
});
console.log(`Client ${id} connected. Total: ${this.clients.size}`);
return id;
}
sendToAll(data, eventType = null) {
const message = eventType
? `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`
: `data: ${JSON.stringify(data)}\n\n`;
this.clients.forEach((client, id) => {
try {
client.write(message);
} catch (error) {
console.error(`Error sending to client ${id}:`, error);
this.clients.delete(id);
}
});
}
broadcast(event, data) {
this.sendToAll(data, event);
}
getClientCount() {
return this.clients.size;
}
}
const sseManager = new SSEClientManager();
// SSE endpoint
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const clientId = sseManager.addClient(res);
// Filter by event type
const filters = req.query.filters?.split(',') || null;
res._sseFilters = filters;
});
// API endpoint that broadcasts events
app.post('/api/broadcast', express.json(), (req, res) => {
const { event, data } = req.body;
sseManager.broadcast(event || 'message', data);
res.json({ sent: sseManager.getClientCount() });
});
// Periodic heartbeat
setInterval(() => {
sseManager.sendToAll({ type: 'heartbeat', time: Date.now() }, 'heartbeat');
}, 30000);
// Status endpoint
app.get('/api/sse-status', (req, res) => {
res.json({
connectedClients: sseManager.getClientCount(),
});
});
app.listen(3000, () => {
console.log('Express SSE server on port 3000');
});
Expected output: Client manager tracks all SSE connections and provides broadcast capabilities to server-side code.
Example 3: Authenticated SSE with Express
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
// Authentication middleware
const authenticateSSE = (req, res, next) => {
const token = req.query.token || req.cookies?.token;
if (!token) {
res.status(401).json({ error: 'Authentication required' });
return;
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(403).json({ error: 'Invalid token' });
}
};
// Per-user SSE endpoint
app.get('/events/user', authenticateSSE, (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const userId = req.user.id;
// Send user-specific events
res.write(`data: ${JSON.stringify({
type: 'connected',
userId,
message: 'Personal event stream started',
})}\n\n`);
// Subscribe to user-specific channels
const userChannel = subscribeToUserChannel(userId, (event) => {
res.write(`event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`);
});
req.on('close', () => {
userChannel.unsubscribe();
});
});
// Role-based SSE
app.get('/events/admin', authenticateSSE, (req, res) => {
if (req.user.role !== 'admin') {
res.status(403).json({ error: 'Admin access required' });
return;
}
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
});
// Admin-only events
const adminChannel = subscribeToAdminChannel((event) => {
res.write(`data: ${JSON.stringify(event)}\n\n`);
});
req.on('close', () => adminChannel.unsubscribe());
});
// Event helpers
function subscribeToUserChannel(userId, callback) {
// Subscribe to message queue for user
const interval = setInterval(() => {
callback({
type: 'notification',
data: { userId, message: 'Check your notifications' },
});
}, 10000);
return { unsubscribe: () => clearInterval(interval) };
}
function subscribeToAdminChannel(callback) {
const interval = setInterval(() => {
callback({
type: 'system-health',
cpu: Math.random() * 100,
memory: Math.random() * 100,
});
}, 5000);
return { unsubscribe: () => clearInterval(interval) };
}
app.listen(3000);
Expected output: Authenticated SSE endpoints that deliver user-specific and role-based events.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Not setting keep-alive headers | Without explicit Connection: keep-alive, proxies may close the connection |
| Forgetting to handle req.on('close') | Resources leak if cleanup is not performed on disconnection |
| Blocking the event loop | SSE handlers should not perform CPU-intensive operations synchronously |
| Not filtering by user | Broadcasting all events to all clients wastes bandwidth; filter server-side |
| Missing CORS headers | Browser SSE clients need appropriate CORS headers for cross-origin access |
Practice Questions
- How do you add an SSE endpoint to an Express application?
- How do you broadcast events to all connected SSE clients?
- How do you authenticate SSE connections in Express?
- How do you implement per-user event filtering?
- How do you handle client disconnections properly?
Challenge
Build an Express application with both REST and SSE endpoints. The REST API allows any authenticated user to trigger events, and the SSE endpoint broadcasts those events to connected clients with proper authorization.
FAQ
Mini Project
Build a real-time activity feed for a team collaboration app using Express SSE. Include per-user event streams, broadcasting for team-wide announcements, and integration with existing authentication middleware.
What's Next
Learn how to implement SSE with Python
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro