devkult_
tools30converters34
home/mime/text/event-stream

text/event-stream

Server-sent eventsstreaming response

text/event-stream

Server-sent events: a long-lived response in which the server pushes messages as lines of text, and the browser's EventSource reads them as they arrive.

MIME typetext/event-stream
Used forStreaming response
Parameterscharset — always UTF-8, so it can be left out
SpecificationHTML Standard: server-sent events

What it looks like on the wire

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

retry: 5000

event: price
id: 41
data: {"symbol":"BTC","usd":67210}

: keep-alive comment

data: a message with the default event type
Content-Type: text/event-stream

Send it from the server

Express
app.get("/events", (req, res) => {
  res.set({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache" });
  res.flushHeaders();

  const timer = setInterval(() => res.write(`data: ${Date.now()}\n\n`), 1000);
  req.on("close", () => clearInterval(timer));
});
Web standard Response (Next.js route handlers, Deno, Bun, Workers)
export async function GET() {
  const enc = new TextEncoder();
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(enc.encode("data: hello\n\n"));
    },
  });
  return new Response(stream, {
    headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
  });
}
nginx in front of the app
location /events {
  proxy_pass http://app;
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_buffering off;      # or send X-Accel-Buffering: no from the app
  proxy_read_timeout 1h;
}

Read it in the client

Browser (EventSource)
const events = new EventSource("/events");
events.onmessage = (e) => console.log(e.data);
events.addEventListener("price", (e) => console.log(JSON.parse(e.data)));
curl (-N turns off output buffering)
curl -N https://example.com/events

Good to know

  • Reverse proxies buffer responses by default, so events arrive in bursts or only when the stream ends. Turn buffering off for the route — proxy_buffering off or an X-Accel-Buffering: no response header for nginx — and make sure compression middleware flushes after each event or skips the stream.
  • EventSource reconnects on its own and sends the last id: it received in a Last-Event-ID request header, so the server can resume where it left off.
  • EventSource can only make GET requests and can't set headers. AI APIs that stream over POST use the same event format, but clients read it with fetch and a stream reader instead.
  • Over HTTP/1.1, browsers allow about six connections per origin, and every open EventSource holds one. HTTP/2 removes that limit.

Frequently asked questions

What is the difference between server-sent events and WebSockets?

Server-sent events are one-way, from server to client, over an ordinary HTTP response, with reconnection built in. WebSockets are two-way and need an upgrade handshake. For notifications, live feeds and streamed AI responses, server-sent events are simpler.

Why do my server-sent events arrive all at once?

Something between the server and the browser is buffering the response, usually a reverse proxy or compression middleware. Disable buffering for the stream (X-Accel-Buffering: no for nginx) and flush after each event.

What do the lines in an event stream mean?

data: carries the payload (several data lines are joined with newlines), event: names the event type, id: sets the ID used for resuming, retry: sets the reconnection delay in milliseconds, and a line starting with : is a comment, often sent as a keep-alive. A blank line ends each event.

Related types

Headers that go with it: Cache-Control · Content-Type · Content-Encoding