A 38-minute working session on pushing data the moment it changes — the WebSocket handshake and frames, the connection lifecycle you must manage (heartbeats, reconnect), the pub/sub and presence patterns realtime apps are built from, how to scale across many servers with a backplane, and when a lighter transport (SSE, long-polling, WebRTC) is the better call — with the real libraries and managed services you'll actually use.
Plain HTTP is request/response: the client asks, the server answers, the connection closes. That's perfect for loading a page — and wrong for anything that changes on its own. Chat, live dashboards, multiplayer cursors, order tracking, notifications: the server knows first, so the server should speak first.
Polling's average latency is half the interval, and most replies are empty. A push arrives once, exactly when there's something to send.
304/empty, yet still cost a round-trip and a DB hit.Like phoning the kitchen every minute to ask if dinner's ready — versus the cook calling you the moment it is.
You don't open a WebSocket out of thin air. It begins as an ordinary HTTP GET carrying an Upgrade header; if the server agrees, the same TCP connection stops speaking HTTP and starts speaking the WebSocket protocol — full-duplex, both sides free to send at any time.
ws:// or, over TLS, wss:// — always prefer wss in production (encrypted, and far more likely to survive proxies).text, binary, ping, pong, or close.onmessage.A frame is mostly payload: a couple of header bytes, then your bytes. No per-message HTTP overhead.
A long-lived connection is a liability as much as a feature: laptops sleep, Wi-Fi flickers, phones change networks, and proxies quietly cut idle connections. Realtime code is mostly about surviving that — detecting a dead socket fast and getting back online cleanly.
open, message, close, error. The two that decide whether your app feels reliable, you add yourself: a heartbeat (to notice a dead link) and reconnect with backoff (to recover without stampeding the server).The happy path is one hop; the real work is the loop — closed → reconnecting → (after a backoff delay) → connecting.
Very few realtime apps are one-to-one. The dominant shape is publish/subscribe: clients subscribe to a named channel, and when something happens the server broadcasts it to everyone subscribed — without the publisher knowing who's listening.
Publish once to a channel; the server fans it out to every current subscriber. The publisher never names the recipients.
Group sockets under a name (room:42, user:7) so a broadcast reaches exactly the right clients — not everyone connected.
Track membership of a channel and emit join/leave. Powers "3 people online", avatars, and live "typing…" indicators.
A consumer that can't keep up forces a choice: buffer (memory risk), drop (lossy), or disconnect. Decide deliberately per channel.
A single process can hold tens of thousands of sockets, but eventually you run more than one. The moment you do, naive broadcast breaks: two users in the same chat may be connected to different servers, and a message published on one never reaches the other.
A message arriving on server A is published to Redis, which delivers it to server B — so B's clients see it too. Every node both publishes and subscribes.
Most of this — stickiness, the backplane, presence, connection limits — is exactly what a managed realtime service handles for you. Build it yourself when you need control; buy it when you'd rather ship.
A full-duplex socket is powerful but heavy to run. Three other transports cover a lot of realtime needs with less operational weight — and one of them (SSE) is the right default for the most common case of all: server→client feeds.
Use when: notifications, live scores, progress, log/AI token streams — anything the client only needs to receive.
The client makes a request the server holds open until there's data (or a timeout), then immediately re-requests. It mimics push using only normal HTTP.
Use when: you need a floor of support; libraries like Socket.IO use it automatically when WebSockets are blocked.
Direct browser-to-browser channels for audio, video, and low-latency data — the server mostly just helps the peers find each other (signaling).
Use when: video/voice calls, screen share, or P2P games where every millisecond counts.
Start from the need: most "live" features are one-way and SSE is enough; reach for a WebSocket when the client must talk back too.
You rarely hand-roll RFC 6455. The real choice is build-vs-buy: a library you run yourself, or a managed service that owns the hard parts (scaling, presence, global delivery). Here are the leading options, each with one honest upside and downside.
A tiny, standards-pure WebSocket implementation for Node. You get raw frames and nothing else.
A higher-level layer with rooms, auto-reconnect, long-poll fallback and a Redis adapter for scaling out.
Standalone, language-agnostic pub/sub servers (Soketi is Pusher-protocol-compatible) you run beside your app.
Hosted channels with presence, message history and delivery guarantees across a global network.
One of the original hosted pub/sub services — quick to wire up for broadcasts and presence.
Open-source service that streams Postgres row changes plus broadcast and presence channels.
How to choose: ws when you want pure WebSockets and will own scaling; Socket.IO for a full toolkit in one app; Centrifugo/Soketi to self-host a scalable bus; Ably/Pusher/Supabase when you'd rather pay to make scaling, presence and global delivery someone else's problem.
Five quick questions on realtime transports, the WebSocket handshake, lifecycle and scaling — instant feedback, no sign-in.
Navigate with ← → or scroll · back to library