toolhq.io

All posts
7 min readby Jameel Haider

WebSockets vs HTTP: when a request is not enough

HTTP is request and response: the client asks, the server answers, the exchange ends. A WebSocket is a persistent, two way connection: after one handshake, either side can send a message at any moment, with no request needed. That single difference decides which one fits: HTTP for fetching and submitting, WebSockets for anything where the server needs to speak first, chat, live prices, multiplayer state, collaborative editing.

What problem do WebSockets solve?

HTTP has no way for a server to start a conversation. If new data appears on the server, a mail notification, a price change, another user's cursor, the server must wait for the client to ask. Before WebSockets, the workarounds were:

  • Polling. Ask every few seconds. Simple, but latency equals the polling interval, and most requests return nothing, pure overhead multiplied across every connected user.
  • Long polling. Ask, and the server holds the request open until it has news, then the client immediately asks again. Better latency, but each message still costs a full HTTP round trip with headers, and held open requests tie up infrastructure.

WebSockets, standardized in 2011, replace the workarounds: one TCP connection, held open, carrying small framed messages in both directions with a couple of bytes of overhead per message instead of hundreds.

How the handshake works

A WebSocket begins life as an ordinary HTTP request, which is the clever part: it travels through the same ports, 80 and 443, and the same load balancers as the rest of the web.

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

A server that speaks WebSocket answers with status 101 Switching Protocols and echoes a hash of the key, and from that moment the TCP connection stops being HTTP entirely: both sides exchange WebSocket frames, text or binary, until one closes the connection. The wss:// scheme is the TLS version, exactly as https:// is to http://, and is the only variant you should ship.

Two consequences of the design are worth knowing:

  • The 101 must come from the server the client reached. Every proxy, load balancer, and CDN in the path has to pass the upgrade through. The classic deployment failure is nginx stripping the Upgrade header, which needs two explicit proxy_set_header lines.
  • Idle connections get killed by middleboxes. Proxies and NATs drop connections that look dead, which is why every WebSocket library sends ping/pong heartbeats.

What each side looks like in code

The browser API is small:

const ws = new WebSocket('wss://example.com/chat')
ws.onopen = () => ws.send(JSON.stringify({ join: 'room-1' }))
ws.onmessage = (event) => render(JSON.parse(event.data))
ws.onclose = () => scheduleReconnect()

Note what the API does not give you: delivery guarantees, automatic reconnection, or message replay. A WebSocket is a pipe, not a queue. Every production app ends up implementing reconnection with backoff, and either resending or resyncing state after a drop. Libraries like Socket.IO exist mostly to package those patterns.

What about Server-Sent Events?

SSE is the middle option people forget: a regular HTTP response that never ends, streaming text events to the client. One direction only, server to client.

PollingSSEWebSocket
DirectionClient pullsServer pushesBoth ways
TransportRepeated HTTPOne HTTP streamUpgraded TCP
Auto reconnectn/aBuilt inYou build it
Works through strict proxiesAlwaysNearly alwaysUsually
FitRarely changing dataFeeds, notifications, AI token streamsChat, games, collaboration

If the client never needs to send anything on the same channel, SSE gets you server push with plain HTTP semantics, built in reconnection, and no proxy drama. It is the right answer more often than its obscurity suggests, and it is what most AI chat interfaces use to stream tokens.

When should you not use WebSockets?

A persistent connection is a cost, not a free upgrade:

  • Scaling changes shape. Stateless HTTP lets any server answer any request. A WebSocket pins each user to one server, so broadcasting to a room means coordinating across servers, typically with Redis pub/sub or a message broker. That is real architecture, not a library import.
  • No HTTP caching. Everything in the caching machinery applies only to requests. Pushed data cannot be cached by CDNs.
  • Ordinary CRUD gains nothing. Fetching a page of results or submitting a form over a WebSocket buys latency savings measured in single milliseconds and costs you status codes, caching, retries, and every debugging tool built for HTTP.

The honest default: build on HTTP, add SSE when the server needs to push, and reach for WebSockets when the conversation is genuinely two way and latency sensitive.

Debugging WebSocket connections

The browser's DevTools Network tab shows the upgrade request with a 101 status, and clicking it reveals a Messages tab with every frame in both directions, the single most useful view when things misbehave. A connection that dies with code 1006 closed abnormally, no close frame, which nearly always means a proxy or timeout killed the TCP underneath rather than either endpoint choosing to close.

The HTTP side of your stack is easy to poke at with the API Tester, and if what you are building is server push in response to external events, check whether a webhook fits before reaching for a socket at all; our webhook debugging guide and the Webhook Inspector cover that path.