Real-time Communication

WebSockets

WebSockets keep one connection open in both directions, so the server can push data the moment it changes instead of waiting to be asked.

intermediate14 min readUpdated Sep 15, 2026
server.js
js
// server.js
import { WebSocketServer } from "ws";

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

wss.on("connection", (socket) => {
  socket.on("message", (data) => {
    for (const client of wss.clients) {
      if (client.readyState === client.OPEN) {
        client.send(data.toString());
      }
    }
  });
});
Started by
HTTP Upgrade handshake
Transport
A TCP connection
Direction
Full duplex
Overhead
Very low per message
Secure
wss:// over TLS
Alternatives
SSE, long polling

Why it matters

Why WebSockets matter

Server can push

The server sends data the instant it happens, with no request from the client, which is essential for chat, presence and live data.

Low latency and overhead

After the handshake, messages have minimal framing overhead and no repeated headers, unlike polling.

Works with the web platform

The browser API is simple, runs over the same ports as HTTP, and upgrades to TLS with wss.

The big picture

The three parts of a WebSocket

An HTTP upgrade, a framed full-duplex connection, and application logic for rooms, presence and reconnection.

The handshake

Upgrade

An HTTP request with an Upgrade header turns into a WebSocket connection.

Frames

Exchange

Small framed messages travel in both directions over one connection.

The server

Manage

Track connections, rooms and presence, and scale with pub/sub.

WebSockets at a glance

The core of WebSockets

Upgrade handshake

The client asks to upgrade an HTTP request; the server responds 101.

Frames

Text and binary messages with a small header, plus control frames.

Full duplex

Both sides can send at any time over the same connection.

Rooms

Group connections so messages go to the right subset.

Heartbeats

Ping and pong detect dead connections and keep proxies from closing them.

Pub/sub

Broadcast across server instances through Redis or another broker.

A short history

From polling to persistent connections

  1. 2008

    Early hacks

    Developers simulate real-time with long polling and comet techniques.

    08
  2. 2011

    WebSocket standardised

    RFC 6455 defines the protocol and browsers add support.

    11
  3. 2012

    Socket.IO popularises it

    A library with fallbacks and rooms brings real-time to mainstream apps.

    12
  4. 2015

    Scaling patterns

    Redis pub/sub and sticky sessions become standard for multi-instance deployments.

    15
  5. Today

    One tool among several

    WebSockets coexist with Server-Sent Events, WebTransport and WebRTC.

    Today

The complete guide

WebSockets: Everything you need to know

What are WebSockets?

A WebSocket is a persistent, full-duplex connection between a client and a server. After an initial HTTP handshake, both sides can send messages at any time over the same TCP connection, with very little per-message overhead. The server can push data the moment something changes instead of waiting for the client to ask.

That makes WebSockets the right tool for chat, presence, live dashboards, collaborative editing, multiplayer games and anything else where updates must arrive immediately. For simple one-way updates, Server-Sent Events are often a simpler choice.

The upgrade handshake

A WebSocket connection begins life as an HTTP request.

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

If the server accepts, it replies:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

After 101 Switching Protocols, the connection is no longer HTTP. It becomes a WebSocket, and data flows in frames. Because it starts as HTTP, it works over the same ports (80 and 443), through most proxies and firewalls, and upgrades to TLS with wss://.

Frames

WebSocket messages are split into frames with a small header. There are text frames, binary frames and control frames for ping, pong and close. The framing is minimal, which is why WebSocket messages are far cheaper than repeated HTTP requests.

The protocol also defines close codes, such as 1000 for a normal closure and 1001 for going away, which help both sides understand why a connection ended.

The browser API

The client API is small and event-driven.

// client.js
const socket = new WebSocket("wss://api.example.com/chat");

socket.addEventListener("open", () => {
  socket.send(JSON.stringify({ type: "join", room: "general" }));
});

socket.addEventListener("message", (event) => {
  const message = JSON.parse(event.data);
  render(message);
});

socket.addEventListener("close", (event) => {
  console.log("closed", event.code, event.reason);
});

socket.addEventListener("error", () => {
  console.error("connection error");
});

You send with socket.send() and receive message events. The readyState property tells you whether the socket is connecting, open, closing or closed.

A Node.js server

The ws library is the standard way to run a WebSocket server in Node.

// server.js
import { WebSocketServer } from "ws";

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

wss.on("connection", (socket, request) => {
  socket.on("message", (data, isBinary) => {
    for (const client of wss.clients) {
      if (client !== socket && client.readyState === client.OPEN) {
        client.send(data, { binary: isBinary });
      }
    }
  });

  socket.on("close", () => console.log("client left"));
});

The server tracks every connection and can send to one client, a subset (a room) or all of them. Socket.IO is a higher-level alternative that adds rooms, acknowledgements, automatic reconnection and fallbacks, at the cost of a custom protocol and client.

Rooms and presence

Real applications rarely broadcast to everyone. You track which connection belongs to which user or room.

// rooms.js
const rooms = new Map();

function join(socket, room) {
  if (!rooms.has(room)) rooms.set(room, new Set());
  rooms.get(room).add(socket);
}

function broadcast(room, payload) {
  for (const socket of rooms.get(room) ?? []) {
    if (socket.readyState === socket.OPEN) socket.send(payload);
  }
}

Rooms, presence (who is online) and typing indicators are all built from this pattern plus heartbeats to detect disconnects.

Heartbeats and reconnection

A connection can die without either side noticing, especially on mobile networks or behind proxies. Two things keep it healthy:

  • Heartbeats. Send a ping every 30 seconds and terminate the connection if no pong arrives. Many proxies also close idle connections, so traffic keeps them open.
  • Reconnection. The client should reconnect with exponential backoff and re-establish its subscriptions. This is where a library like Socket.IO saves effort.
// heartbeat.js
const interval = setInterval(() => {
  for (const socket of wss.clients) {
    if (socket.isAlive === false) return socket.terminate();
    socket.isAlive = false;
    socket.ping();
  }
}, 30_000);

wss.on("close", () => clearInterval(interval));

Scaling

A WebSocket connection lives on exactly one server instance, so broadcasting across instances needs a shared channel.

  • Pub/sub: publish messages to Redis, NATS or another broker, and have every instance deliver them to its local clients.
  • Sticky sessions: if your infrastructure requires them, keep a client on the same instance for the life of the connection.
  • State in a shared store: keep presence and room membership in Redis so any instance can answer.
  • Backpressure and limits: cap connections per instance and monitor memory, because each connection holds a socket and some state.

Security

  • Always use wss:// in production so traffic is encrypted.
  • Authenticate during the handshake and reject unauthorised connections.
  • Validate the Origin header to prevent cross-site WebSocket hijacking.
  • Authorise every message; never trust that a connected client is allowed to do something.
  • Limit message size and rate to prevent abuse.
  • Set timeouts so abandoned connections do not leak.

Best practices

  • Use WebSockets for bidirectional, low-latency updates and SSE for one-way push.
  • Heartbeat and reconnect; never assume a connection is alive.
  • Authenticate during the handshake and authorise every message.
  • Keep per-connection state small and rooms in a shared store.
  • Use pub/sub to broadcast across instances.
  • Set message size and rate limits.
  • Terminate idle connections and monitor connection counts.

Common mistakes

  • Using WebSockets for simple request/response where HTTP would do.
  • Forgetting heartbeats and leaking dead connections.
  • Broadcasting to every client when only a room should receive.
  • Storing authoritative state only in memory and losing it on restart.
  • Skipping origin checks and message authorisation.
  • Assuming the load balancer will preserve the connection without configuration.

Where to go next

WebSockets are the persistent-connection layer on top of TCP. Ground them in the HTTP guide and TCP/IP & Sockets, run them on Node.js, and compare the push model with GraphQL subscriptions. Then build a small chat room with rooms and heartbeats to see the patterns in practice.

Receiving live updates

A WebSocket pushes changes as they happen. Polling repeats requests and wastes bandwidth, especially when nothing changes.

Prefer
const socket = new WebSocket("wss://api.example.com/feed");

socket.addEventListener("message", (event) => {
  render(JSON.parse(event.data));
});
Avoid
setInterval(async () => {
  const res = await fetch("/api/feed");
  render(await res.json());
}, 1000);

Detecting dead connections

A dropped connection is often invisible to both sides. Heartbeats detect it and trigger reconnection.

Prefer
const alive = setInterval(() => {
  if (socket.readyState !== socket.OPEN) return;
  socket.ping();
}, 30_000);
Avoid
// assume the connection is
// alive forever; dead peers
// leak resources silently

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning WebSockets?

Our interactive tutorial walks you through WebSockets step by step — with quizzes and real code you can run in the browser.