WebSockets Cheatsheet

Sending and Receiving

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

Sending Text

// Raw string
ws.send("Hello!");

// JSON (most common pattern)
ws.send(JSON.stringify({ type: "chat", text: "Hello", ts: Date.now() }));

// Template literal
ws.send(`ping:${Date.now()}`);

Sending Binary

ArrayBuffer / TypedArray

// Send a typed array
const arr = new Uint8Array([0x01, 0x02, 0x03, 0xff]);
ws.send(arr.buffer);          // send the underlying ArrayBuffer
ws.send(arr);                 // ws (Node) also accepts TypedArray directly

// Structured binary with DataView
const buf = new ArrayBuffer(8);
const view = new DataView(buf);
view.setUint32(0, 42, false);       // big-endian
view.setFloat32(4, 3.14, false);
ws.send(buf);

Node Buffer

// Node.js server
ws.send(Buffer.from("hello"));
ws.send(Buffer.from([0x01, 0x02, 0x03]));
ws.send(Buffer.alloc(16));           // zero-filled
ws.send(Buffer.from(someString, "utf8"));

Blob (browser only)

const blob = new Blob([JSON.stringify(obj)], { type: "application/json" });
ws.send(blob);

Sending with a Callback (ws / Node)

// ws library: 3rd argument is a callback called after the data is flushed
ws.send(data, { binary: false, compress: true }, (err) => {
  if (err) console.error("failed to send:", err);
  else console.log("sent and flushed");
});

Receiving Text

// Browser
ws.addEventListener("message", ({ data }) => {
  if (typeof data === "string") {
    const msg = JSON.parse(data);   // if JSON
    console.log(msg.type, msg.payload);
  }
});

// Node (ws library) — data is Buffer by default
ws.on("message", (data, isBinary) => {
  if (!isBinary) {
    const text = data.toString("utf8");
    const msg = JSON.parse(text);
  }
});

Receiving Binary

// Browser — prefer arraybuffer for direct byte access
ws.binaryType = "arraybuffer";
ws.addEventListener("message", ({ data }) => {
  if (data instanceof ArrayBuffer) {
    const view = new DataView(data);
    const id = view.getUint32(0, false);
    const value = view.getFloat32(4, false);
  }
});

// Node — data arrives as Buffer
ws.on("message", (data, isBinary) => {
  if (isBinary) {
    const id = data.readUInt32BE(0);
    const value = data.readFloatBE(4);
  }
});

Discriminating Message Types

String prefix

ws.on("message", (data) => {
  const text = data.toString();
  if (text.startsWith("ping:")) return ws.send("pong:" + text.slice(5));
  if (text.startsWith("msg:"))  return handleChat(text.slice(4));
});

JSON envelope (most common)

// Both sides agree on { type, payload } shape
ws.on("message", (raw) => {
  const { type, payload } = JSON.parse(raw.toString());
  switch (type) {
    case "join":   handleJoin(payload); break;
    case "chat":   handleChat(payload); break;
    case "leave":  handleLeave(payload); break;
    default: console.warn("unknown type:", type);
  }
});

First-byte opcode (binary protocol)

ws.on("message", (data, isBinary) => {
  if (!isBinary) return;
  const op = data[0];          // first byte is opcode
  switch (op) {
    case 0x01: handleMove(data.slice(1)); break;
    case 0x02: handleShot(data.slice(1)); break;
  }
});

Fragmented / Large Messages

The ws library reassembles fragments automatically. For very large payloads, stream instead:

// Streaming a large file over WebSocket (Node ws)
import { createReadStream } from "fs";

const stream = createReadStream("large-file.bin");
stream.on("data", (chunk) => {
  if (ws.readyState === WebSocket.OPEN) ws.send(chunk);
});

Backpressure — Don't Flood the Socket

// Check bufferedAmount before sending (browser)
function drainSend(ws, messages) {
  for (const msg of messages) {
    if (ws.bufferedAmount > 65536) {
      // defer remaining messages
      setTimeout(() => drainSend(ws, messages.slice(messages.indexOf(msg))), 50);
      return;
    }
    ws.send(msg);
  }
}

// Node ws — pause/resume the stream
ws.pause();              // stop firing message events
// ... do slow async work ...
ws.resume();             // resume

Sending JSON Safely

function send(ws, type, payload) {
  if (ws.readyState !== WebSocket.OPEN) return;
  try {
    ws.send(JSON.stringify({ type, payload, ts: Date.now() }));
  } catch (err) {
    console.error("send error:", err);
  }
}

send(ws, "chat", { text: "Hello", room: "general" });

Receiving All Messages — Server Broadcast

// Echo everything back to sender
wss.on("connection", (ws) => {
  ws.on("message", (data, isBinary) => {
    ws.send(data, { binary: isBinary });   // echo in same format
  });
});

Binary Encoding Formats

FormatLibraryUse case
Raw bytesSimple fixed-layout structs (game state)
JSON textbuilt-inFlexibility, debuggability
MessagePackmsgpackr, @msgpack/msgpackCompact JSON-equivalent binary
Protocol BuffersprotobufjsStrongly-typed, versioned schemas
CBORcbor-xSelf-describing binary, like JSON
FlatBuffersflatbuffersZero-copy access, gaming/HFT

Gotchas

  • In the browser, send() with readyState !== OPEN throws InvalidStateError — always guard.
  • Node ws message data is Buffer, not string — always call .toString() for text or check isBinary.
  • JSON.parse throws on malformed input — wrap in try/catch for untrusted data.
  • Large binary payloads should use streams or chunking; a single 100 MB send() will queue the entire buffer before flushing.
  • ws.send() is synchronous in terms of queueing but asynchronous in terms of I/O — use the callback or bufferedAmount to know when data has left the kernel buffer.