WebSockets Cheatsheet

Client API

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

Constructor

// Basic
const ws = new WebSocket("wss://example.com/ws");

// With subprotocols
const ws = new WebSocket("wss://example.com/ws", "json");
const ws = new WebSocket("wss://example.com/ws", ["json", "msgpack"]);

The browser WebSocket constructor takes a URL string and an optional protocol string or array. No option object — use query params for extra data.

Properties Reference

PropertyTypeDescription
ws.urlstringThe URL passed to the constructor
ws.readyStatenumberCurrent connection state (see below)
ws.bufferedAmountnumberBytes queued to send but not yet transmitted
ws.protocolstringSubprotocol selected by the server (or "")
ws.extensionsstringExtensions negotiated (e.g. "permessage-deflate")
ws.binaryTypestring"blob" (default) or "arraybuffer"
WebSocket.CONNECTING0Static constant
WebSocket.OPEN1Static constant
WebSocket.CLOSING2Static constant
WebSocket.CLOSED3Static constant

readyState Values

switch (ws.readyState) {
  case WebSocket.CONNECTING: // 0 — handshake in progress
  case WebSocket.OPEN:       // 1 — ready to use
  case WebSocket.CLOSING:    // 2 — close handshake started
  case WebSocket.CLOSED:     // 3 — connection closed
}

Methods

send(data)

ws.send("hello world");                   // text (string)
ws.send(JSON.stringify({ type: "msg" })); // JSON as text
ws.send(new ArrayBuffer(8));              // binary
ws.send(new Uint8Array([1, 2, 3]));       // typed array
ws.send(blob);                            // Blob

Call only when readyState === WebSocket.OPEN. Calling while CONNECTING throws InvalidStateError.

close([code[, reason]])

ws.close();              // code 1000, no reason
ws.close(1000);          // normal closure
ws.close(1001, "Bye");   // with reason string (max 123 bytes UTF-8)
ws.close(4001, "Custom app code");

After calling close(), readyState moves to CLOSING. The close event fires when the handshake completes.

Events

Event Listener Pattern (recommended)

ws.addEventListener("open", (event) => {
  console.log("connected");
});

ws.addEventListener("message", (event) => {
  console.log("data:", event.data);        // string, Blob, or ArrayBuffer
  console.log("origin:", event.origin);    // server origin
  console.log("lastEventId:", event.lastEventId);
});

ws.addEventListener("error", (event) => {
  // event is a generic Event — no error details exposed (security)
  console.error("WebSocket error");
});

ws.addEventListener("close", (event) => {
  console.log("code:", event.code);        // number
  console.log("reason:", event.reason);    // string
  console.log("wasClean:", event.wasClean); // boolean
});

Handler Properties (alternative)

ws.onopen    = (event) => { /* ... */ };
ws.onmessage = (event) => { /* ... */ };
ws.onerror   = (event) => { /* ... */ };
ws.onclose   = (event) => { /* ... */ };

addEventListener supports multiple handlers per event; handler properties do not. Prefer addEventListener.

MessageEvent Properties

PropertyTypeDescription
event.datastring | Blob | ArrayBufferMessage payload
event.originstringServer origin ("wss://example.com")
event.lastEventIdstringAlways "" for WebSocket
event.sourcenullAlways null for WebSocket

CloseEvent Properties

PropertyTypeDescription
event.codenumberClose code (see codes table in Basics)
event.reasonstringHuman-readable reason
event.wasCleanbooleantrue if close handshake completed properly

Receiving Binary Data

// Default: data arrives as Blob
ws.binaryType = "blob";
ws.onmessage = async ({ data }) => {
  if (data instanceof Blob) {
    const text = await data.text();
    const buffer = await data.arrayBuffer();
  }
};

// Prefer ArrayBuffer for direct typed-array access
ws.binaryType = "arraybuffer";
ws.onmessage = ({ data }) => {
  if (data instanceof ArrayBuffer) {
    const view = new DataView(data);
    console.log(view.getUint8(0));
  }
};

Checking bufferedAmount Before Sending

const MAX_BUFFER = 1024 * 64; // 64 KB

function throttledSend(ws, data) {
  if (ws.bufferedAmount > MAX_BUFFER) {
    console.warn("send buffer full, dropping frame");
    return;
  }
  ws.send(data);
}

bufferedAmount does not decrease in real time in all browsers — check it just before send().

Passing Auth Without Headers

The browser does not allow setting custom headers on WebSocket connections.

// Option 1: Query parameter
const ws = new WebSocket(`wss://api.example.com/ws?token=${encodeURIComponent(jwt)}`);

// Option 2: Cookie (sent automatically if same origin / SameSite allows it)
// Set-Cookie: hu_session=...; HttpOnly; Secure; SameSite=None
const ws = new WebSocket("wss://api.example.com/ws");

// Option 3: First message after open (protocol-level auth)
ws.addEventListener("open", () => {
  ws.send(JSON.stringify({ type: "auth", token: jwt }));
});

Full Minimal Client Example

class ChatClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
  }

  connect() {
    this.ws = new WebSocket(this.url);
    this.ws.binaryType = "arraybuffer";

    this.ws.addEventListener("open", () => this.onOpen());
    this.ws.addEventListener("message", (e) => this.onMessage(e));
    this.ws.addEventListener("error", (e) => this.onError(e));
    this.ws.addEventListener("close", (e) => this.onClose(e));
  }

  send(data) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(typeof data === "string" ? data : JSON.stringify(data));
    }
  }

  onOpen() { console.log("connected"); }
  onMessage({ data }) { console.log("msg:", data); }
  onError() { console.error("error"); }
  onClose({ code, wasClean }) {
    console.log(`closed ${wasClean ? "cleanly" : "abruptly"} with code ${code}`);
  }

  disconnect() { this.ws?.close(1000, "User left"); }
}

Gotchas

  • The browser exposes no error detail in the error event (intentional — prevents port scanning). Check server logs.
  • ws.send() throws InvalidStateError only while CONNECTING; on CLOSING/CLOSED it silently discards the data. Guard with readyState === WebSocket.OPEN so messages are not dropped without a trace.
  • Calling new WebSocket() immediately begins the handshake — you cannot set binaryType before open fires and have it affect initial messages; set it right after construction instead.
  • Tab or browser close triggers code 1001 ("going away") — the beforeunload event fires before the close frame completes.
  • CORS does not apply to WebSockets. The Origin header is sent but the browser does not enforce same-origin — validate it server-side.