WebSockets Cheatsheet

Socket.IO Comparison

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

What Socket.IO Is

Socket.IO is a library on top of WebSocket (with HTTP long-polling fallback) that adds: rooms, namespaces, acknowledgements, automatic reconnection, and broadcast helpers. It is not a drop-in compatible WebSocket client — a Socket.IO server rejects a plain new WebSocket() client.

Installation

# Server
npm install socket.io

# Client (browser build also available via CDN)
npm install socket.io-client

Basic Setup — Server

import { createServer } from "http";
import { Server } from "socket.io";
import express from "express";

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: { origin: "https://example.com", methods: ["GET", "POST"] },
});

io.on("connection", (socket) => {
  console.log("connected:", socket.id);

  socket.on("chat", (data) => {
    io.to("general").emit("chat", data); // broadcast to room
  });

  socket.on("disconnect", (reason) => {
    console.log("disconnected:", reason);
  });
});

httpServer.listen(4000);

Basic Setup — Client

import { io } from "socket.io-client";

const socket = io("https://api.example.com", {
  path: "/socket.io",          // default
  transports: ["websocket"],   // skip polling entirely
  auth: { token: jwt },        // server reads socket.handshake.auth.token
  reconnectionDelayMax: 30_000,
  autoConnect: true,
});

socket.on("connect",    () => console.log("id:", socket.id));
socket.on("disconnect", (reason) => console.log("reason:", reason));
socket.on("chat",       (data)   => console.log("msg:", data));

socket.emit("chat", { text: "Hello", room: "general" });

Emitting Events

// Server side
socket.emit("event", payload);                 // to this socket only
socket.to(roomId).emit("event", payload);      // to room, exclude sender
io.to(roomId).emit("event", payload);          // to room, include sender
io.emit("event", payload);                     // broadcast to all
socket.broadcast.emit("event", payload);       // all except this socket
io.to(roomId).except(socket.id).emit(...);     // to room, except sender (v4)
io.to([roomA, roomB]).emit("event", payload);  // multiple rooms at once

// Client side
socket.emit("event", payload);
socket.emit("event", payload, callback);       // with ack

Acknowledgements (Acks)

// Client emits with ack callback
socket.emit("save", { text: "note" }, (response) => {
  console.log("server ack:", response); // { ok: true, id: 42 }
});

// Server replies via ack function
io.on("connection", (socket) => {
  socket.on("save", (data, ack) => {
    const id = saveToDb(data);
    ack({ ok: true, id }); // call ack to reply
  });
});

// Server emits with ack (v4.6+)
const response = await io.to(socketId).emitWithAck("ping");
// Client: socket.on("ping", (ack) => ack("pong"));

Rooms

// Server — join / leave
socket.join("room-42");        // join
socket.leave("room-42");       // leave
socket.rooms;                  // Set — rooms this socket is in (always includes socket.id)

// Auto room: socket.id is a single-socket room (for private messages)
io.to(socket.id).emit("private", { text: "only you" });

// Broadcast to room
io.to("room-42").emit("update", data);
socket.to("room-42").emit("update", data);  // exclude sender

// Leave all rooms (socket does this automatically on disconnect)
for (const room of socket.rooms) socket.leave(room);

Namespaces

// Server
const chatNs  = io.of("/chat");
const adminNs = io.of("/admin");

chatNs.on("connection", (socket) => {
  socket.emit("welcome", "chat namespace");
});

adminNs.use((socket, next) => {
  if (isAdmin(socket.handshake.auth.token)) next();
  else next(new Error("Unauthorized"));
});

// Client
const chatSocket  = io("https://api.example.com/chat");
const adminSocket = io("https://api.example.com/admin", { auth: { token } });

Middleware (Server)

// Global middleware — runs for every socket before connection event
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  try {
    socket.user = jwt.verify(token, SECRET);
    next();
  } catch {
    next(new Error("Authentication error"));
  }
});

// Namespace middleware
io.of("/admin").use((socket, next) => {
  if (!socket.user?.isAdmin) return next(new Error("Forbidden"));
  next();
});

Socket Properties

PropertyDescription
socket.idUnique socket identifier (string)
socket.roomsSet<string> of rooms joined
socket.handshake{ headers, query, auth, address, time, url }
socket.handshake.authObject from client auth option
socket.handshake.queryQuery params from connection URL
socket.connectedboolean
socket.dataPlain object for app data (persists across reconnects in v4)

Server io Methods

MethodDescription
io.emit(event, data)Broadcast to all namespaces/sockets
io.to(room).emit(...)Emit to a room
io.except(room).emit(...)Emit to all except a room
io.socketsJoin(room)Make all sockets join a room
io.socketsLeave(room)Make all sockets leave a room
io.disconnectSockets()Disconnect all sockets
io.fetchSockets()Get all socket objects (async)
io.in(room).fetchSockets()Get sockets in a room (async)

io Constructor Options (selection)

OptionDefaultDescription
transports["polling","websocket"]Transport order
pingInterval25000ms between pings
pingTimeout20000ms to wait for pong
maxHttpBufferSize1e6Max message size in bytes
corsCORS options passed to cors package
path"/socket.io"HTTP path for Engine.IO
adapterdefault in-memory@socket.io/redis-adapter for multi-server

Redis Adapter (multi-server)

npm install @socket.io/redis-adapter redis
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis"; // node-redis

const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);

io.adapter(createAdapter(pub, sub));
// Now io.to("room").emit(...) works across all server instances

With ioredis instead (auto-connects — no .connect() call, no createClient export):

import { createAdapter } from "@socket.io/redis-adapter";
import Redis from "ioredis";

const pub = new Redis(process.env.REDIS_URL);
const sub = pub.duplicate();
io.adapter(createAdapter(pub, sub));

Socket.IO vs Raw ws — Feature Comparison

FeatureRaw wsSocket.IO
TransportWebSocket onlyWebSocket + HTTP polling fallback
Auto-reconnectManualBuilt-in
RoomsManual MapBuilt-in
NamespacesManual path routingBuilt-in
Acks / request-replyManualBuilt-in
BinaryYesYes (with type detection)
Broadcast helpersManualBuilt-in
Multi-serverManual RedisRedis adapter
Bundle size (client)0 (browser native)~45 KB gzipped
InteropAny WS clientSocket.IO client only
ComplexityLow (you build)Low (provided)

When to Use Which

Use ws (raw)Use Socket.IO
You control both client and serverYou need broad browser/proxy support
Minimal overhead is criticalYou want rooms/acks out of the box
You don't need fallback transportsRapid prototyping
Binary protocol (games, streaming)Chat, notifications, collaborative tools
Embedding in a larger system (e.g., editor plugins)Teams less familiar with low-level WS

Gotchas

  • A plain new WebSocket(url) from the browser cannot connect to a Socket.IO server — the handshake is incompatible.
  • socket.emit and io.emit do not throw on error — use ack callbacks or emitWithAck for confirmation.
  • The default client transport order is still ["polling", "websocket"] — every new connection starts with an HTTP long-polling request, then upgrades. Set transports: ["websocket"] in performance-sensitive apps to skip the polling round-trips (at the cost of losing the fallback for proxies that block WebSocket).
  • The disconnect event fires on the server with a reason string ("transport close", "ping timeout", "server namespace disconnect", etc.) — log it for debugging.
  • socket.data is per-process in-memory; with the Redis adapter, only emits are synchronized — not socket.data.