Skip to main content
Technology & EngineeringWebsocket178 lines

Websocket Basics

WebSocket API fundamentals including connection lifecycle, message framing, and browser/server implementations

Quick Summary18 lines
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 lines
Paste into your CLAUDE.md or agent config

WebSocket 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 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)

Connection States

The WebSocket.readyState property reflects the current state:

  • 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

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, and id fields for routing and correlation.
  • Set reasonable message size limits to prevent memory exhaustion (e.g., maxPayload option in the ws library).
  • Handle backpressure — check ws.bufferedAmount before 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 maxPayload or 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

Get CLI access →