WebSockets Cheatsheet

Server with ws (Node)

Use this WebSockets reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Installation

npm install ws
npm install --save-dev @types/ws   # TypeScript

Package: `ws` — the de-facto Node.js WebSocket library. Zero runtime dependencies.

Standalone Server

import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 4000 });

wss.on("connection", (ws, req) => {
  const ip = req.socket.remoteAddress;
  console.log(`Client connected from ${ip}`);

  ws.on("message", (data, isBinary) => {
    const text = isBinary ? data : data.toString();
    console.log("received:", text);
  });

  ws.on("close", (code, reason) => {
    console.log("closed:", code, reason.toString());
  });

  ws.on("error", (err) => {
    console.error("ws error:", err);
  });

  ws.send("Hello from server");
});

wss.on("error", (err) => console.error("server error:", err));
wss.on("listening", () => console.log("WebSocket server on :4000"));

WebSocketServer Constructor Options

OptionTypeDefaultDescription
portnumberBind to this port (creates internal HTTP server)
serverhttp.ServerAttach to existing server (share port)
hoststring"0.0.0.0"Bind address
pathstringOnly accept upgrades on this path
maxPayloadnumber104857600Max message size in bytes (100 MB)
backlognumber511TCP listen backlog
clientTrackingbooleantrueMaintain wss.clients Set
perMessageDeflateboolean|objectfalseEnable permessage-deflate compression
skipUTF8ValidationbooleanfalseSkip UTF-8 validation for text frames
handleProtocolsFunctionSubprotocol negotiation callback
verifyClientFunctionReject connections before upgrade (discouraged — see below)
noServerbooleanfalseManual upgrade handling

WebSocket Instance Properties and Methods

Every ws object passed to connection event:

Properties

PropertyTypeDescription
ws.readyStatenumberCONNECTING=0 OPEN=1 CLOSING=2 CLOSED=3
ws.protocolstringNegotiated subprotocol
ws.urlstring(client only) The URL passed to constructor
ws.bufferedAmountnumberBytes queued but not yet sent
ws.binaryTypestring"nodebuffer" (ws) / "blob"|"arraybuffer" (browser)
ws.isPausedbooleanWhether the stream is paused

Methods

MethodDescription
ws.send(data[, options][, cb])Send a message
ws.close([code[, reason]])Initiate close handshake
ws.terminate()Destroy the socket immediately (no close frame)
ws.ping([data][, mask][, cb])Send a ping frame
ws.pong([data][, mask][, cb])Send a pong frame
ws.pause()Pause receiving messages
ws.resume()Resume receiving messages

ws.send() Options

ws.send("hello");                          // text
ws.send(Buffer.from([0x01, 0x02]));        // binary
ws.send(data, { binary: true });           // force binary
ws.send(data, { compress: false });        // skip per-message deflate
ws.send(data, { fin: false });             // start fragmented message
ws.send(data, { fin: true });              // last fragment
ws.send(data, (err) => {                   // delivery callback
  if (err) console.error("send failed:", err);
});

WebSocketServer Properties and Methods

Property/MethodDescription
wss.clientsSet<WebSocket> of all open connections
wss.address(){ address, family, port }
wss.close([cb])Stop accepting connections; cb fires when all closed
wss.handleUpgrade(req, socket, head, cb)Manual upgrade (noServer mode)
wss.shouldHandle(req)Returns true if the server should handle the request

verifyClient — Reject Before Upgrade (discouraged)

The ws maintainers discourage verifyClient and will not extend it — the recommended pattern is noServer: true plus the HTTP server's upgrade event: authenticate the request yourself, socket.destroy() (or write a raw HTTP response) on failure, and call wss.handleUpgrade() only on success. See the Sharing a Port with Express page for the full pattern.

const wss = new WebSocketServer({
  port: 4000,
  verifyClient({ origin, req, secure }, callback) {
    const token = new URL(req.url, "http://x").searchParams.get("token");
    if (!isValidToken(token)) {
      callback(false, 401, "Unauthorized");
    } else {
      callback(true);
    }
  },
});

verifyClient is called synchronously when given one argument, asynchronously with two arguments (preferred).

Per-Message Deflate (Compression)

const wss = new WebSocketServer({
  port: 4000,
  perMessageDeflate: {
    zlibDeflateOptions: { chunkSize: 1024, memLevel: 7, level: 3 },
    zlibInflateOptions: { chunkSize: 10 * 1024 },
    clientNoContextTakeover: true,   // saves memory, reduces ratio
    serverNoContextTakeover: true,
    serverMaxWindowBits: 10,
    concurrencyLimit: 10,            // max concurrent zlib ops
    threshold: 1024,                 // only compress if > 1 KB
  },
});

Compression helps for text-heavy JSON payloads (50–70% reduction). Disable for already-compressed binary data (images, video).

Server Events Reference

EventCallback signatureWhen
connection(ws, req)New client connected
error(error)Server-level error
listening()Server bound and ready
close()Server closed
headers(headers, req)Before upgrade response (add custom headers)

headers — Add Custom Response Headers

wss.on("headers", (headers, req) => {
  headers.push("Set-Cookie: session=abc; HttpOnly; SameSite=Strict");
  headers.push("X-Custom: value");
});

Client (ws) Events Reference

EventCallback signatureWhen
open()Connection established
message(data, isBinary)Message received
close(code, reason)Connection closed
error(error)Error occurred
ping(data)Ping received
pong(data)Pong received
unexpected-response(req, res)Non-101 upgrade response (ws client mode)
upgrade(response)HTTP upgrade response received

ws as a Client (Node-to-Node)

import WebSocket from "ws";

const ws = new WebSocket("wss://api.example.com/ws", {
  headers: { Authorization: `Bearer ${token}` },  // allowed in Node, not browser
  handshakeTimeout: 5000,
  rejectUnauthorized: true,       // validate TLS cert (true in prod)
  perMessageDeflate: false,
});

ws.on("open", () => ws.send("hello"));
ws.on("message", (data) => console.log(data.toString()));
ws.on("error", console.error);
ws.on("close", (code) => console.log("closed:", code));

Checking readyState Before Sending

function safeSend(ws, data) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(data);
  }
}

Gotchas

  • ws.terminate() drops the TCP connection without a close frame — the client sees code 1006. Use ws.close() for graceful shutdown.
  • wss.clients contains all WebSockets, including those in CLOSING state — check readyState === WebSocket.OPEN before sending.
  • message data is a Buffer by default in Node; call .toString() for text or set ws.binaryType = "arraybuffer" for ArrayBuffer.
  • The error event must be handled — an unhandled error event crashes the Node process.
  • clientTracking: false is a micro-optimization; only use it when you maintain your own set to avoid iterating wss.clients.