Websocket Basics
WebSocket API fundamentals including connection lifecycle, message framing, and browser/server implementations
You are an expert in the WebSocket protocol and API for building real-time applications. ## Key Points - `Sec-WebSocket-Key` — a base64-encoded random value from the client - `Sec-WebSocket-Accept` — server proof derived from the key using a fixed GUID and SHA-1 - `Sec-WebSocket-Protocol` — optional subprotocol negotiation - `Sec-WebSocket-Version` — protocol version (13 for RFC 6455) - `0` (CONNECTING) — connection not yet established - `1` (OPEN) — connection is open and ready - `2` (CLOSING) — connection is going through the closing handshake - `3` (CLOSED) — connection is closed or could not be opened - **Always use `wss://`** (WebSocket Secure) in production to encrypt traffic and avoid proxy interference. - **Implement heartbeats** to detect dead connections; TCP keep-alive alone is often insufficient. - **Use structured message formats** — define a message envelope with `type`, `payload`, and `id` fields for routing and correlation. - **Set reasonable message size limits** to prevent memory exhaustion (e.g., `maxPayload` option in the `ws` library).
skilldb get websocket-skills/Websocket BasicsFull skill: 178 linesWebSocket Basics — WebSockets & Real-Time
You are an expert in the WebSocket protocol and API for building real-time applications.
Overview
WebSockets provide full-duplex, persistent communication channels over a single TCP connection. Unlike HTTP request-response, WebSockets allow both client and server to send messages independently at any time, making them ideal for real-time applications such as chat, live dashboards, and multiplayer games.
Core Concepts
The WebSocket Handshake
WebSocket connections begin with an HTTP upgrade request. The client sends an Upgrade: websocket header, and the server responds with a 101 Switching Protocols status to establish the persistent connection.
Key handshake headers:
Sec-WebSocket-Key— a base64-encoded random value from the clientSec-WebSocket-Accept— server proof derived from the key using a fixed GUID and SHA-1Sec-WebSocket-Protocol— optional subprotocol negotiationSec-WebSocket-Version— protocol version (13 for RFC 6455)
Connection States
The WebSocket.readyState property reflects the current state:
0(CONNECTING) — connection not yet established1(OPEN) — connection is open and ready2(CLOSING) — connection is going through the closing handshake3(CLOSED) — connection is closed or could not be opened
Message Types
WebSocket frames carry either text (UTF-8) or binary (ArrayBuffer/Blob) data. Control frames include ping, pong, and close.
Implementation Patterns
Browser Client
const ws = new WebSocket('wss://example.com/socket');
ws.addEventListener('open', () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
});
ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
});
ws.addEventListener('close', (event) => {
console.log(`Closed: code=${event.code} reason=${event.reason} clean=${event.wasClean}`);
});
ws.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});
Node.js Server with ws
import { WebSocketServer } from 'ws';
import { createServer } from 'http';
const server = createServer();
const wss = new WebSocketServer({ server });
wss.on('connection', (ws, request) => {
const ip = request.socket.remoteAddress;
console.log(`Client connected from ${ip}`);
ws.on('message', (data, isBinary) => {
const message = isBinary ? data : data.toString();
console.log('Received:', message);
// Echo back
ws.send(JSON.stringify({ echo: message, timestamp: Date.now() }));
});
ws.on('close', (code, reason) => {
console.log(`Client disconnected: ${code}`);
});
// Send a welcome message
ws.send(JSON.stringify({ type: 'welcome', server: 'v1.0' }));
});
server.listen(8080, () => {
console.log('WebSocket server running on ws://localhost:8080');
});
Sending Binary Data
// Client: sending binary
const buffer = new ArrayBuffer(8);
const view = new Float64Array(buffer);
view[0] = 3.14159;
ws.send(buffer);
// Client: receiving binary
ws.binaryType = 'arraybuffer'; // or 'blob'
ws.addEventListener('message', (event) => {
if (event.data instanceof ArrayBuffer) {
const view = new Float64Array(event.data);
console.log('Received float:', view[0]);
}
});
Heartbeat / Keep-Alive
// Server-side heartbeat using ws library
function heartbeat() {
this.isAlive = true;
}
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => clearInterval(interval));
Best Practices
- Always use
wss://(WebSocket Secure) in production to encrypt traffic and avoid proxy interference. - Implement heartbeats to detect dead connections; TCP keep-alive alone is often insufficient.
- Use structured message formats — define a message envelope with
type,payload, andidfields for routing and correlation. - Set reasonable message size limits to prevent memory exhaustion (e.g.,
maxPayloadoption in thewslibrary). - Handle backpressure — check
ws.bufferedAmountbefore sending large payloads to avoid overwhelming slow clients. - Authenticate during the handshake — validate tokens via query params or cookies before accepting the upgrade, not after.
Common Pitfalls
- Ignoring close codes — the close code (1000 = normal, 1001 = going away, 1006 = abnormal) provides critical debugging information. Always log it.
- Not handling connection drops — browsers do not automatically reconnect. You must implement reconnection logic yourself.
- Assuming message ordering across connections — messages within a single WebSocket are ordered, but if you reconnect, there may be gaps.
- Mixing text and binary carelessly — pick one format per message type and document it. Parsing binary data as text silently corrupts data.
- Blocking the event loop on the server — a slow message handler delays all other clients on that process. Offload heavy work to a worker thread or queue.
- Forgetting to close connections — leaked connections consume file descriptors and memory. Always clean up on process shutdown.
Core Philosophy
WebSockets provide a persistent, full-duplex communication channel — and that persistence is both their greatest strength and their greatest responsibility. Unlike HTTP request-response, a WebSocket connection lives for the duration of the session, consuming server resources (file descriptors, memory, CPU) continuously. Every connection must be actively managed: monitored for health via heartbeats, cleaned up on disconnect, and bounded by message size limits and backpressure controls.
Authentication happens during the handshake, not after. By the time the WebSocket connection is established, the server has already committed resources to it. Validate tokens, session cookies, or API keys during the HTTP upgrade request so that unauthorized connections are rejected before they consume resources. Authenticating after the connection is open means the server is already handling potentially malicious traffic.
Define a message protocol from the start. Every message should have a type field for routing, a payload field for data, and optionally an id field for correlation and acknowledgement. Without this structure, your message handling degrades into a tangle of if/else conditions checking message shape. A well-defined protocol is the equivalent of an API schema for real-time communication.
Anti-Patterns
-
Not implementing heartbeats — relying on TCP keep-alive alone allows dead connections to linger for minutes; application-level ping/pong detects failures within seconds.
-
Mixing text and binary message types carelessly — sending binary data and parsing it as text (or vice versa) silently corrupts data; establish a clear convention per message type and enforce it.
-
Assuming automatic reconnection — unlike SSE, the browser WebSocket API does not reconnect automatically; you must implement reconnection logic with backoff and state recovery yourself.
-
Not setting message size limits — a malicious client can send arbitrarily large messages that exhaust server memory; always configure
maxPayloador equivalent limits. -
Ignoring WebSocket close codes — the close code (1000 = normal, 1001 = going away, 1006 = abnormal) is critical debugging information; always log the code and reason to diagnose connection issues.
Install this skill directly: skilldb add websocket-skills
Related Skills
Chat Rooms
Chat room architecture covering message routing, history, moderation, and scalable room-based real-time systems
Collaborative Editing
Real-time collaborative editing with CRDTs and Operational Transform for conflict-free concurrent document editing
Presence
User presence system design for tracking online/offline status, typing indicators, and activity in real-time apps
Reconnection
Reconnection and offline resilience patterns for WebSocket apps including retry strategies and state synchronization
Scaling Websockets
Scaling WebSocket applications with Redis pub/sub, sticky sessions, horizontal scaling, and load balancing strategies
Server Sent Events
Server-Sent Events (SSE) patterns for efficient unidirectional real-time streaming from server to client