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
| Property | Description |
|---|---|
socket.id | Unique socket identifier (string) |
socket.rooms | Set<string> of rooms joined |
socket.handshake | { headers, query, auth, address, time, url } |
socket.handshake.auth | Object from client auth option |
socket.handshake.query | Query params from connection URL |
socket.connected | boolean |
socket.data | Plain object for app data (persists across reconnects in v4) |
Server io Methods
| Method | Description |
|---|---|
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)
| Option | Default | Description |
|---|---|---|
transports | ["polling","websocket"] | Transport order |
pingInterval | 25000 | ms between pings |
pingTimeout | 20000 | ms to wait for pong |
maxHttpBufferSize | 1e6 | Max message size in bytes |
cors | — | CORS options passed to cors package |
path | "/socket.io" | HTTP path for Engine.IO |
adapter | default 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
| Feature | Raw ws | Socket.IO |
|---|---|---|
| Transport | WebSocket only | WebSocket + HTTP polling fallback |
| Auto-reconnect | Manual | Built-in |
| Rooms | Manual Map | Built-in |
| Namespaces | Manual path routing | Built-in |
| Acks / request-reply | Manual | Built-in |
| Binary | Yes | Yes (with type detection) |
| Broadcast helpers | Manual | Built-in |
| Multi-server | Manual Redis | Redis adapter |
| Bundle size (client) | 0 (browser native) | ~45 KB gzipped |
| Interop | Any WS client | Socket.IO client only |
| Complexity | Low (you build) | Low (provided) |
When to Use Which
Use ws (raw) | Use Socket.IO |
|---|---|
| You control both client and server | You need broad browser/proxy support |
| Minimal overhead is critical | You want rooms/acks out of the box |
| You don't need fallback transports | Rapid 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.emitandio.emitdo not throw on error — use ack callbacks oremitWithAckfor confirmation.- The default client transport order is still
["polling", "websocket"]— every new connection starts with an HTTP long-polling request, then upgrades. Settransports: ["websocket"]in performance-sensitive apps to skip the polling round-trips (at the cost of losing the fallback for proxies that block WebSocket). - The
disconnectevent fires on the server with areasonstring ("transport close","ping timeout","server namespace disconnect", etc.) — log it for debugging. socket.datais per-process in-memory; with the Redis adapter, only emits are synchronized — notsocket.data.