The Problem with HTTP for Real-Time Apps
HTTP is a stateless, request-response protocol. A client sends a request, the server responds, and the connection closes. For most web use cases — loading pages, submitting forms, fetching data — this works perfectly.
But consider a live chat application. If User A sends a message to User B, how does User B's browser know to display it? With plain HTTP, User B's browser has no way of knowing unless it asks. The traditional workaround is polling: the client repeatedly sends HTTP requests every few seconds asking "any new messages?" This is wasteful, slow, and scales poorly — imagine 10,000 users polling a server every 2 seconds.
Long polling was an improvement: the server holds the request open until data arrives, then responds. Better, but still fundamentally a series of HTTP request-response cycles with overhead on each. WebSockets were designed to solve this properly.
What is a WebSocket?
A WebSocket is a communication protocol (RFC 6455) that provides a full-duplex, persistent connection between a client and server over a single TCP connection. "Full-duplex" means both sides can send messages independently and simultaneously — neither party has to wait for the other.
Once a WebSocket connection is established, messages flow freely in both directions with minimal overhead. There are no headers sent with every message (unlike HTTP), just a small frame header of 2–14 bytes. This makes WebSockets dramatically more efficient than any polling approach for high-frequency communication.
WebSocket connections use the ws:// scheme (unencrypted) or wss:// (encrypted, uses TLS — always use this in production). The WebSocket API is standardised by the WHATWG and supported natively in all modern browsers.
How WebSockets Work: The Handshake
A WebSocket connection starts with an HTTP upgrade handshake. The client sends a special HTTP request with an Upgrade header:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
If the server supports WebSockets, it responds with a 101 Switching Protocols status:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After this handshake, the protocol "upgrades" from HTTP to the WebSocket protocol. The TCP connection that carried the HTTP upgrade remains open, and now both sides speak the WebSocket framing protocol. From this point, messages travel as frames — lightweight binary packets that carry your data with minimal overhead.
WebSockets in JavaScript
The browser WebSocket API is clean and event-driven. Here is a complete example:
// Open a connection
const socket = new WebSocket('wss://example.com/chat');
// Connection opened
socket.addEventListener('open', () => {
console.log('Connected to server');
socket.send(JSON.stringify({ type: 'join', room: 'general' }));
});
// Receive messages
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Message from server:', data);
});
// Handle errors
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
// Connection closed
socket.addEventListener('close', (event) => {
console.log('Disconnected. Code:', event.code, 'Reason:', event.reason);
});
Sending data is just socket.send(data). You can send strings or binary data (ArrayBuffer, Blob). Most applications serialize to JSON for ease of use.
On the server side (Node.js), popular libraries include the ws package (lightweight, no frills), Socket.IO (adds rooms, namespaces, automatic fallback), and uWebSockets.js (extremely high performance).
reconnecting-websocket handle this automatically with exponential backoff.
WebSocket vs Alternatives
| Method | Direction | Connection | Overhead | Best For |
|---|---|---|---|---|
| HTTP Polling | Client → Server | New each time | Very high | Nothing — avoid |
| Long Polling | Server → Client | New each message | High | Low-frequency updates |
| Server-Sent Events (SSE) | Server → Client only | Persistent | Low | Notifications, feeds |
| WebSockets | Bidirectional | Persistent | Very low | Chat, games, collaboration |
| WebRTC | Peer-to-peer | Direct | Low (after setup) | Video, audio, P2P data |
Server-Sent Events (SSE) are a simpler alternative when you only need server-to-client push. SSE uses a regular HTTP connection and is natively supported in browsers via EventSource. It handles reconnection automatically and works well for notifications, news feeds, and dashboards where the client doesn't need to send data frequently. However, SSE is one-directional — the server pushes, the client listens.
WebRTC is for peer-to-peer communication — browser to browser without a server in the middle for the data path. It is the technology behind video calling (Google Meet, Zoom for web) and can also transfer arbitrary data. The setup complexity is much higher.
Real-World Use Cases
Chat Applications
The quintessential WebSocket use case. When a user sends a message, the server receives it and broadcasts it to all connected clients in the room instantly. Applications like Slack, Discord, and Microsoft Teams use persistent connections for message delivery.
Live Collaboration
Google Docs, Figma, and Notion all use some form of WebSocket communication to sync document changes across multiple users in real time. Every keystroke or cursor movement is transmitted to collaborators with sub-second latency.
Multiplayer Games
Browser-based games require continuous position updates, player state synchronization, and game events. WebSockets provide the low-latency bidirectional channel that makes this possible.
Live Financial Data
Stock tickers, cryptocurrency prices, and trading platforms push price updates multiple times per second. Polling at that frequency would be catastrophic; WebSockets handle it efficiently.
Real-Time Notifications
Social media platforms, project management tools, and e-commerce sites use WebSockets (or SSE) to deliver instant notifications without page refreshes.
Scaling and Considerations
WebSockets introduce architectural considerations that HTTP APIs don't have. Each open connection consumes server resources. A single Node.js server can typically handle tens of thousands of concurrent WebSocket connections, but horizontal scaling requires a message broker (like Redis Pub/Sub or a managed service like Pusher) so that a message received by one server instance reaches clients connected to other instances.
Load balancers need to support sticky sessions or WebSocket proxying — not all do by default. Nginx supports WebSocket proxying with the proxy_http_version 1.1 and Upgrade/Connection headers set.
For most applications that aren't at massive scale, managed WebSocket services (Pusher, Ably, AWS API Gateway WebSocket APIs) abstract all of this complexity away.