Real-Time Backend

Server-Sent Events

Цели урока

  • Understand how SSE works over HTTP (chunked encoding, text/event-stream)
  • Use the four format fields: data, event, id, retry
  • Build an SSE server in Node.js with auto-reconnect and Last-Event-ID
  • Choose between SSE, WebSocket, and polling based on requirements

Предварительные знания

  • HTTP and Its Limitations

2013. Twitter moved its live feed from polling to SSE - server load dropped 10x. One HTTP request replaced thousands of repeats, and new tweets surfaced instantly. 2022: ChatGPT shipped its typing effect on SSE, streaming each token as its own event. SSE is the first real push technology baked straight into the browser.

  • Facebook/Twitter - live feeds and notifications via SSE
  • GitHub - real-time CI/CD log streaming
  • Financial platforms - quote and price updates
  • ChatGPT - streaming model responses (the typing effect via SSE)

Ian Hickson and the HTML5 EventSource

Ian Hickson, lead editor of the HTML5 specification at WHATWG, added Server-Sent Events to the HTML5 draft in 2006. Before that, the only way to receive server events was polling or long polling - workarounds bolted on top of HTTP. Hickson wanted browsers to ship a native push mechanism without leaning on WebSocket, which lived in a separate spec. In 2014 SSE was published as a standalone W3C standard.

SSE: Push over ordinary HTTP

2013. Twitter moves its live feed from polling to SSE - server load drops 10x. One HTTP request replaces thousands of repeats. ChatGPT in 2022 uses SSE for the typing effect, streaming each token as its own event. That is SSE: the server pushes events to the client, not the other way around.

0

1

Sign In

**Server-Sent Events (SSE)** is a W3C-standard mechanism where the server pushes events to the client over a plain HTTP connection. The client fires one GET request, the server keeps the connection open, and streams data the moment it has data to send.

SSE rides on HTTP/1.1 chunked transfer encoding. No special protocol - just ordinary HTTP. As a result, SSE slips through any proxy or firewall with zero extra configuration.

  • **Unidirectional:** server -> client only (unidirectional)
  • **Content-Type:** text/event-stream
  • **Transport:** standard HTTP/1.1, chunked encoding
  • **Auto-reconnect:** the browser reconnects automatically on disconnect
  • **Built into the browser:** EventSource API, no libraries needed

Polling has the client hammer the server with requests. SSE is **real push**: the server decides when to send. One connection opens, then lives until one side closes it.

SSE is a bidirectional channel like WebSocket

SSE is strictly unidirectional: only the server can send data to the client. Sending data from the client requires a separate HTTP request

What Content-Type is used for SSE?

The text/event-stream format

An SSE response is plain text with simple formatting rules. No binary protocol, no handshakes. That is a headline advantage: SSE is directly debuggable in the browser's Network tab.

An event is a bundle of fields separated by a double newline (`\n\n`). Four format fields:

FieldPurposeExample
data:Event data (required)data: {"price": 100}
event:Event name (default: "message")event: notification
id:Unique ID for recovery after disconnectid: 42
retry:Reconnection time in msretry: 3000

Every event **must** terminate with a double `\n\n`. A single `\n` separates fields inside one event. Drop the double newline and the browser cannot detect event boundaries.

What happens if the `event:` field is omitted from an SSE event?

EventSource API in the browser

Server ready. The browser connects through the built-in **EventSource** API - no npm install, no webpack, no dependencies. A rare case where the platform ships the complete tool.

readyStateValueDescription
CONNECTING0Connecting or reconnecting
OPEN1Connection active, data flowing
CLOSED2Connection closed, no reconnection

**Auto-reconnect and Last-Event-ID** are SSE's superpower. When the connection drops, the browser reconnects automatically and sends a `Last-Event-ID` header carrying the last received `id:`. The server uses that to replay missed events.

Default reconnection interval is browser-dependent (usually 3-5 seconds). The server can override it with `retry: 5000` in milliseconds.

After calling source.close(), the same connection can be reopened

An EventSource object transitions to CLOSED state permanently after close(). A new EventSource instance must be created for a new connection

What happens when an SSE connection drops, if the server was sending an `id:` field with every event?

SSE limitations and when to use it

SSE is a sharp tool with hard limits. Knowing those limits is the difference between picking the right technology and fighting the wrong one.

  • **Unidirectional:** server -> client only. Sending data from the client requires a separate POST/PUT
  • **Limit of 6 connections** per domain in HTTP/1.1 (a browser limit, not a protocol one). In HTTP/2 this limit is lifted via multiplexing
  • **Text only:** binary data (images, audio) cannot be sent. Base64 encoding is possible, but adds +33% to the size
  • **No Authorization header** in the EventSource constructor. Authentication must use cookies or query parameters
  • **Does not work in Service Workers** (API restriction, not a protocol restriction)
CriterionPollingLong PollingSSE
DirectionClient -> ServerClient -> ServerServer -> Client
LatencyPolling intervalLowMinimal (real push)
Server loadHigh (many requests)MediumLow (one connection)
Auto-reconnectManualManualBuilt into browser
Binary dataYesYesNo (text only)
BidirectionalYes (new request)Yes (new request)No

When SSE is the ideal choice

  • SSE fits — Live news and social media feeds, notifications (browser push), real-time metric dashboards, log streaming, price and quote updates, progress for long-running operations (upload, processing), streaming LLM responses
  • SSE does not fit — Chats and messengers (bidirectional exchange needed), online games (low latency in both directions needed), video/audio streaming (binary data), collaborative editing like Google Docs

HTTP/2 does not enforce the 6-connection-per-domain limit - every SSE stream is multiplexed over a single TCP connection. Once the server speaks HTTP/2, the connection cap disappears.

For which scenario is SSE the best fit?

Key SSE Ideas

  • SSE - unidirectional push from server to client over ordinary HTTP
  • text/event-stream format: data:, event:, id:, retry: fields separated by \n\n
  • EventSource API - built into the browser, with auto-reconnect and Last-Event-ID
  • Limit of 6 connections per domain in HTTP/1.1 (lifted in HTTP/2)
  • Ideal for live feeds, notifications, dashboards, and streaming LLM responses

Related Topics

SSE is the first step toward true real-time. The next step - WebSocket - adds a bidirectional channel.

  • WebSocket — The next step - full-duplex protocol for bidirectional exchange
  • HTTP/2 Server Push — An alternative push mechanism at the protocol level
  • Long Polling — The predecessor - SSE as its evolution

Вопросы для размышления

  • Why did ChatGPT developers choose SSE for streaming responses rather than WebSocket?
  • How would SSE connection authentication be implemented in a production application?
  • In what scenarios are storing last-event-id and replaying missed events critical?

Связанные уроки

  • rt-02-http-limits — Polling and long polling - the backdrop that makes SSE necessary
  • rt-04 — WebSocket - the next step: a bidirectional channel
  • rt-05 — HTTP/2 Server Push - an alternative push mechanism at the protocol level
  • rt-11 — Streaming LLM responses (ChatGPT typing effect) is built on SSE
  • stream-03 — Message brokers as event sources feeding SSE endpoints
  • rt-06 — WebRTC for scenarios where SSE is insufficient
  • net-22-http-headers
  • net-21-http-basics
Server-Sent Events