jsonssestreamingapi

Server-Sent Events (SSE): Streaming JSON to the Browser

·9 min read·APIs

The simplest way to stream

When the server needs to push a steady stream of updates to the browser — a live feed, a progress bar, or the tokens of a streaming AI response — Server-Sent Events (SSE) is often the right tool. It runs over plain HTTP, auto-reconnects, and is dead simple compared to WebSockets. It is the transport behind most streaming LLM APIs, and JSON is what travels over it.

The wire format

SSE is a long-lived HTTP response with a special content type and a tiny text format: each message is a data: line, terminated by a blank line.

text
Content-Type: text/event-stream

data: {"token":"Hello"}

data: {"token":" world"}

data: [DONE]

Each data: payload is typically a JSON object. The double newline separates events. That is the whole protocol — no framing library required.

Consuming SSE in the browser

The native EventSource API handles connection and reconnection for you:

javascript
const es = new EventSource("/api/stream");

es.onmessage = (e) => {
  if (e.data === "[DONE]") { es.close(); return; }
  const msg = JSON.parse(e.data);   // each event is JSON
  appendToken(msg.token);
};

es.onerror = () => { /* EventSource auto-reconnects */ };

For POST requests or custom headers (common with AI APIs), you read the stream manually with fetch and parse data: lines yourself.

Producing SSE on the server

javascript
// Node/Next.js route handler
const stream = new ReadableStream({
  async start(controller) {
    const enc = new TextEncoder();
    for (const token of tokens) {
      controller.enqueue(enc.encode(`data: ${JSON.stringify({ token })}\n\n`));
    }
    controller.enqueue(enc.encode("data: [DONE]\n\n"));
    controller.close();
  },
});
return new Response(stream, { headers: { "Content-Type": "text/event-stream" } });

Each chunk is data: <json> followed by two newlines.

SSE vs WebSockets

AspectSSEWebSockets
DirectionServer → client onlyBidirectional
ProtocolPlain HTTPUpgrade to ws://
ReconnectAutomaticManual
FormatText (UTF-8)Text or binary
Best forFeeds, notifications, AI token streamsChat, multiplayer, collaboration

If you only need the server to push to the client — which is exactly the streaming-AI case — SSE is simpler and more robust. Reach for WebSockets when the client must also send a continuous stream.

Gotchas

  • Each event needs the trailing blank line, or the browser buffers it.
  • Disable proxy/server buffering (e.g. X-Accel-Buffering: no behind nginx) so tokens flush immediately.
  • Send periodic keep-alive comments (: ping) on idle streams to keep connections open.
  • One JSON object per event — do not split a JSON value across events.

Frequently asked questions

Because responses flow one direction (server to client), and SSE streams small JSON deltas over plain HTTP with automatic reconnection — exactly what token-by-token generation needs.

Use SSE for server-to-client streaming (feeds, notifications, AI tokens). Use WebSockets when you also need a continuous client-to-server channel, like chat or multiplayer.

Each event is a data: line containing a JSON string, ended by a blank line. The client parses each event's payload with JSON.parse.

Try JSON Lines / NDJSON

Work with line-delimited JSON for streaming events.