Real-Time APIs · Real-Time Transports

Server-Sent Events

Simple, HTTP-native server push with automatic reconnection.

SSE streams text/event-stream responses: each event has optional id, event (type), retry and data lines. Browsers' EventSource reconnects automatically and sends Last-Event-ID so servers can resume. SSE works over HTTP/1.1 and HTTP/2 (multiplexed — avoiding the old 6-connections-per-domain limit), passes through most proxies (disable buffering), and is the standard transport for streaming LLM responses.

SSE vs WebSockets

Criterion SSE WebSockets
Direction Server → client Both
Reconnect + resume Built in (Last-Event-ID) Implement yourself
Custom headers from browser No (EventSource); use cookies or fetch-based clients Limited
Binary data No (text) Yes
Proxy/CDN compatibility High Varies
app.get("/events", async (req, res) => {
  res.set({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "X-Accel-Buffering": "no" });
  res.flushHeaders();
  const since = req.get("Last-Event-ID");
  for (const e of await events.since(req.user.id, since)) res.write(`id: ${e.id}\nevent: ${e.type}\ndata: ${JSON.stringify(e.data)}\n\n`);
  const unsubscribe = bus.subscribe(req.user.id, (e) => res.write(`id: ${e.id}\nevent: ${e.type}\ndata: ${JSON.stringify(e.data)}\n\n`));
  const ping = setInterval(() => res.write(": ping\n\n"), 15_000);
  req.on("close", () => { clearInterval(ping); unsubscribe(); });
});

Common pitfalls

  • Proxy buffering delays events (disable it).
  • Not storing events for Last-Event-ID resume.
  • HTTP/1.1 connection limits with many tabs (prefer HTTP/2).

Key points

  • text/event-stream with ids
  • Automatic reconnect + resume
  • Disable buffering; send keep-alives

Further reading: HTML Living Standard: Server-sent events

Practice questions

How does SSE resume after a dropped connection?

Answer

EventSource reconnects automatically and sends the last received event ID in the Last-Event-ID header; the server replays events after that ID.

Why must you disable proxy buffering for SSE?

Answer

Buffering proxies (e.g., NGINX) hold the response until it's complete or a buffer fills, so events wouldn't reach the client in real time.

Open in the app to mark lessons complete, take notes and track your progress.