Server-Sent Events
Push server-to-client updates over a persistent HTTP connection with Server-Sent Events (SSE).
Search across all documentation pages
Push server-to-client updates over a persistent HTTP connection with Server-Sent Events (SSE).
Quick-reference recipe card - copy-paste ready.
import express from "express";
const app = express();
app.get("/events", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
req.on("close", () => clearInterval(interval));
});When to reach for this: Live feeds, notifications, progress updates, and log streaming where only the server sends data.
import express from "express";
const app = express();
const clients = new Set<import("node:http").ServerResponse>();
app.get("/events", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"X-Accel-Buffering": "no", // disable nginx buffering
});
res.write(": connected\n\n"); // comment line keeps connection alive
clients.add(res);
req.on("close", () => {
clients.delete(res);
});
});
function broadcast(event: string, data: unknown) {
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
for (const client of clients) {
client.write(message);
}
}
// Trigger from elsewhere in the app
app.post("/notify", express.json(), (req, res) => {
broadcast("notification", req.body);
res.json({ sent: clients.size });
});Client (browser):
const source = new EventSource("/events");
source.addEventListener("notification", (e) => {
console.log(JSON.parse(e.data));
});
source.onerror = () => console.log("SSE reconnecting...");What this demonstrates:
event: fieldEventSource with auto-reconnectContent-Type: text/event-streamdata: ...\n\n (double newline terminates message)event:, id:, retry:EventSource reconnects automatically with Last-Event-IDevent: price-update
id: 42
data: {"symbol": "AAPL", "price": 182.50}
location /events {
proxy_pass http://node_backend;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}proxy_buffering off and X-Accel-Buffering: no.close.ERR_STREAM_DESTROYED. Fix: check res.writableEnded before write.| Alternative | Use When | Don't Use When |
|---|---|---|
| WebSocket | Bidirectional messaging | Server-only push |
| Long-polling | Very old proxy constraints | Modern infrastructure |
| Socket.IO | Need rooms and fallback | Simple one-way push |
| Webhook to client | Not applicable (no server push) | Real-time UI updates |
SSE. Dashboards are server-to-client. SSE is simpler, HTTP-friendly, and auto-reconnects.
Similar to WebSocket: 10k-50k idle connections. Active broadcast reduces capacity.
Yes, but check timeout limits. Cloudflare may close long-lived connections after 100 seconds on free plan.
Cookie-based auth works with EventSource. For token auth, pass as query param (less secure) or use fetch-based SSE polyfill.
Yes via reply.raw for direct stream write. Or use @fastify/sse plugin.
retry: 5000\n\n tells the browser to wait 5 seconds before reconnecting after disconnect.
Redis pub/sub: each instance subscribes and writes to its local SSE clients. See Scaling Real-Time.
SSE works over HTTP/2. Multiplexing helps when the same connection serves other requests.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026