HTTP is a request-response protocol: the client asks, the server answers, the connection closes. That works perfectly for loading web pages and APIs, but it's terrible for real-time features like chat, live notifications, collaborative editing, stock tickers, or multiplayer games. WebSockets solve this by opening a persistent, bidirectional connection between client and server.
How WebSockets Work
A WebSocket connection starts as an ordinary HTTP request — the "handshake." The client sends an Upgrade: websocket header, and if the server supports it, it responds with a 101 Switching Protocols status. From that point on, both sides can send messages to each other at any time, without the overhead of new HTTP requests.
Client → Server: GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 Server → Client: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
HTTP vs WebSockets
- HTTP: Client-initiated, request-response, stateless, high overhead per message (headers resent each time)
- WebSocket: Bidirectional, persistent, stateful, minimal per-message overhead (2-14 bytes framing)
- SSE (Server-Sent Events): Server-to-client only, built on HTTP, simpler than WebSockets for one-way data streams
Browser Implementation
const ws = new WebSocket("wss://api.example.com/ws");
// Connection opened
ws.addEventListener("open", () => {
console.log("Connected!");
ws.send(JSON.stringify({ type: "join", room: "general" }));
});
// Listen for messages
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data);
});
// Connection closed
ws.addEventListener("close", (event) => {
console.log(`Disconnected: ${event.code} ${event.reason}`);
});
// Handle errors
ws.addEventListener("error", (error) => {
console.error("WebSocket error:", error);
});
Node.js Server with ws
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
console.log("Client connected");
ws.on("message", (message) => {
const data = JSON.parse(message);
// Broadcast to all connected clients
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify({
type: "message",
text: data.text,
timestamp: Date.now()
}));
}
});
});
ws.on("close", () => {
console.log("Client disconnected");
});
});
When to Use WebSockets
- ✅ Use WebSockets for: Chat apps, live notifications, collaborative editing (Google Docs-style), multiplayer games, live sports scores, stock tickers, IoT dashboards
- ❌ Don't use WebSockets for: Standard CRUD APIs, file uploads, infrequent data polling (use SSE or long-polling instead), simple notification badges
Production Considerations
- Reconnection logic: Connections drop. Implement exponential backoff reconnection on the client.
- Heartbeats (ping/pong): Send periodic pings to detect dead connections. Stale connections waste server memory.
- Authentication: Pass a JWT token in the connection URL or first message. Don't rely on cookies alone.
- Scaling: WebSocket connections are stateful. Use a pub/sub system (Redis, NATS) to share messages across multiple server instances.
- Load balancers: Not all load balancers handle WebSocket upgrades correctly. Ensure your LB supports sticky sessions or WebSocket protocol.
Try Our Free Developer Tools
Encode, decode, and format your code instantly.