SSE vs WebSocket: Pick the Right Stream

Server-sent events stream text from server to browser over an ordinary HTTP response, and the EventSource API reconnects by itself. A WebSocket upgrades the connection into a two-way channel that carries text or binary in both directions. Use SSE for feeds, notifications and progress; use WebSockets when the browser must send frequent messages back.

Side by side
FeatureServer-sent eventsWebSocket
DirectionServer to client onlyBoth ways at once
Browser APIEventSourceWebSocket
ProtocolPlain HTTP, text/event-streamHTTP Upgrade, then ws:// or wss://
DataUTF-8 text onlyText or binary
ReconnectAutomatic, resumes with Last-Event-IDWrite it yourself
Open connections on HTTP/16 per browser per domainSeparate limits, set by each browser

Updated

Not sure which one your feature needs? Tick the statements that are true for it in the helper below. It applies the same rules as the table above and says which API fits and why.

Which one fits your feature?

Server-sent events fit. Nothing you ticked needs a two-way channel, and EventSource gives you reconnection and resume for free.

Server-sent events: minimal code

SSE server in plain Node

No library needed. The response stays open; each write is one event, and the comment line keeps idle proxies from closing it.

import { createServer } from "node:http";

let n = 0;
createServer((req, res) => {
  if (req.url !== "/events") return res.writeHead(404).end();
  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
  res.write("retry: 3000\n\n");
  const tick = setInterval(() => {
    n += 1;
    res.write(`id: ${n}\nevent: price\ndata: {"value": ${100 + n}}\n\n`);
  }, 2000);
  const ping = setInterval(() => res.write(": keep-alive\n\n"), 20000);
  req.on("close", () => { clearInterval(tick); clearInterval(ping); });
}).listen(3000);

EventSource in the browser

addEventListener uses the event name from the stream. Reconnection happens without any code of yours.

const source = new EventSource("/events");

source.addEventListener("price", (e) => {
  const { value } = JSON.parse(e.data);
  document.querySelector("#price").textContent = value;
});

source.onerror = () => {
  console.log("Stream dropped; the browser will retry");
};

WebSocket: minimal code

WebSocket server with the ws package

Install with npm install ws. This echoes every message back in upper case to the client that sent it.

import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (socket) => {
  socket.on("error", console.error);
  socket.on("message", (data) => {
    socket.send(String(data).toUpperCase());
  });
  socket.send("connected");
});

WebSocket in the browser, with a simple reconnect

Unlike EventSource, nothing reconnects for you, so this waits a moment after close and opens a new socket.

function connect() {
  const socket = new WebSocket("ws://localhost:8080");
  socket.addEventListener("open", () => socket.send("hello"));
  socket.addEventListener("message", (e) => console.log(e.data));
  socket.addEventListener("close", () => setTimeout(connect, 2000));
  return socket;
}
connect();

How server-sent events work

The server answers a normal GET with Content-Type text/event-stream and keeps the response open. Each message is a few lines such as event:, data: and id:, ended by a blank line. Several data: lines in one message are joined with newlines.

The browser's EventSource parses that stream, fires a message event for unnamed messages and a named event for the rest, and reconnects on its own after a drop. A retry: line sets the wait in milliseconds before it tries again.

How WebSockets work

The client starts with an HTTP request carrying an Upgrade header, and the server answers 101 Switching Protocols. From then on the connection carries WebSocket frames, not HTTP, and either side can send at any time.

That freedom has a cost. You write your own reconnect logic, your own message format, often JSON with a type field, and your own heartbeat. A proxy that does not pass the Upgrade header along will also stop the connection before it starts.

A quick rule

If the browser mostly listens, choose SSE. Live scores, build logs, order status, notifications and streamed text all fit.

If the browser talks as much as it listens, or the data is binary, choose a WebSocket. Multiplayer games, collaborative editors and chat rooms all fit there.

How it works

  1. Tick each statement that describes your feature.
  2. Read the recommendation and the reason it gives.
  3. Copy the matching client and server code from the sections further down.
  4. Change the URL and event names to your own and run the server with Node.

Tips

Frequently asked questions

Can the browser send data to the server with SSE?

Not on the stream itself. The event stream only flows from server to client, so the browser sends its messages with ordinary fetch requests. For chat-like traffic in both directions, a WebSocket is the cleaner fit.

How do I stop EventSource from reconnecting?

Call close() on the client. From the server side, the HTML standard says a response of 204 No Content tells the client to stop reconnecting.

Can I send an Authorization header with EventSource?

No. The constructor takes only a URL and an options object whose one setting is withCredentials. Use a cookie, a short-lived token in the URL, or a fetch-based reader if you need custom headers.

Is SSE outdated now that WebSockets exist?

No. The text/event-stream format is still a good choice for one-way feeds because it runs through normal HTTP servers, proxies and auth with no protocol switch.

Which one uses fewer server resources?

Both keep one long-lived connection per client, so the difference is small. What matters more is whether your host allows long-lived responses or protocol upgrades at all, so check its limits on request duration first.

Related: WebRTC vs WebSocket, GraphQL subscriptions, Express.js tutorial, JavaScript async and fetch, CORS explained.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap