WebSocket Engineer
Core Workflow
- Analyze requirements — Identify connection scale, message volume, latency needs
- Design architecture — Plan clustering, pub/sub, state management, failover
- Implement — Build WebSocket server with authentication, rooms, events
- Validate locally — Test connection handling, auth, and room behavior before scaling (e.g.,
npx wscat -c ws://localhost:3000); confirm auth rejection on missing/invalid tokens, room join/leave events, and message delivery
- Scale — Verify Redis connection and pub/sub round-trip before enabling the adapter; configure sticky sessions and confirm with test connections across multiple instances; set up load balancing
- Monitor — Track connections, latency, throughput, error rates; add alerts for connection-count spikes and error-rate thresholds
Reference Guide
Load detailed guidance based on context:
| Topic |
Reference |
Load When |
| Protocol |
references/protocol.md |
WebSocket handshake, frames, ping/pong, close codes |
| Scaling |
references/scaling.md |
Horizontal scaling, Redis pub/sub, sticky sessions |
| Patterns |
references/patterns.md |
Rooms, namespaces, broadcasting, acknowledgments |
| Security |
references/security.md |
Authentication, authorization, rate limiting, CORS |
| Alternatives |
references/alternatives.md |
SSE, long polling, when to choose WebSockets |
Code Examples
Server Setup (Socket.IO with Auth and Room Management)
import { createServer } from "http";
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
import jwt from "jsonwebtoken";
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: process.env.ALLOWED_ORIGIN, credentials: true },
pingTimeout: 20000,
pingInterval: 25000,
});
// Authentication middleware — runs before connection is established
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error("Authentication required"));
try {
socket.data.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
next(new Error("Invalid token"));
}
});
// Redis adapter for horizontal scaling
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
io.on("connection", (socket) => {
const { userId } = socket.data.user;
console.log(`connected: ${userId} (${socket.id})`);
// Presence: mark user online
pubClient.hSet("presence", userId, socket.id);
socket.on("join-room", (roomId) => {
socket.join(roomId);
socket.to(roomId).emit("user-joined", { userId });
});
socket.on("message", ({ roomId, text }) => {
io.to(roomId).emit("message", { userId, text, ts: Date.now() });
});
socket.on("disconnect", () => {
pubClient.hDel("presence", userId);
console.log(`disconnected: ${userId}`);
});
});
httpServer.listen(3000);
Client-Side Reconnection with Exponential Backoff
import { io } from "socket.io-client";
const socket = io("wss://api.example.com", {
auth: { token: getAuthToken() },
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000, // initial delay (ms)
reconnectionDelayMax: 30000, // cap at 30 s
randomizationFactor: 0.5, // jitter to avoid thundering herd
});
// Queue messages while disconnected
let messageQueue = [];
socket.on("connect", () => {
console.log("connected:", socket.id);
// Flush queued messages
messageQueue.forEach((msg) => socket.emit("message", msg));
messageQueue = [];
});
socket.on("disconnect", (reason) => {
console.warn("disconnected:", reason);
if (reason === "io server disconnect") socket.connect(); // manual reconnect
});
socket.on("connect_error", (err) => {
console.error("connection error:", err.message);
});
function sendMessage(roomId, text) {
const msg = { roomId, text };
if (socket.connected) {
socket.emit("message", msg);
} else {
messageQueue.push(msg); // buffer until reconnected
}
}
Constraints
MUST DO
- Use sticky sessions for load balancing (WebSocket connections are stateful — requests must route to the same server instance)
- Implement heartbeat/ping-pong to detect dead connections (TCP keepalive alone is insufficient)
- Use rooms/namespaces for message scoping rather than filtering in application logic
- Queue messages during disconnection windows to avoid silent data loss
- Plan connection limits per instance before scaling horizontally
MUST NOT DO
- Store large state in memory without a clustering strategy (use Redis or an external store)
- Mix WebSocket and HTTP on the same port without explicit upgrade handling
- Forget to handle connection cleanup (presence records, room membership, in-flight timers)
- Skip load testing before production — connection-count spikes behave differently from HTTP traffic spikes
Output Templates
When implementing WebSocket features, provide:
- Server setup (Socket.IO/ws configuration)
- Event handlers (connection, message, disconnect)
- Client library (connection, events, reconnection)
- Brief explanation of scaling strategy
Knowledge Reference
Socket.IO, ws, uWebSockets.js, Redis adapter, sticky sessions, nginx WebSocket proxy, JWT over WebSocket, rooms/namespaces, acknowledgments, binary data, compression, heartbeat, backpressure, horizontal pod autoscaling
1---2name: websocket-engineer3description: Use when building real-time communication systems with WebSockets or Socket.IO. Invoke for bidirectional messaging, horizontal scaling with Redis, presence tracking, room management.4license: MIT5---67# WebSocket Engineer89## Core Workflow10111. **Analyze requirements** — Identify connection scale, message volume, latency needs122. **Design architecture** — Plan clustering, pub/sub, state management, failover133. **Implement** — Build WebSocket server with authentication, rooms, events144. **Validate locally** — Test connection handling, auth, and room behavior before scaling (e.g., `npx wscat -c ws://localhost:3000`); confirm auth rejection on missing/invalid tokens, room join/leave events, and message delivery155. **Scale** — Verify Redis connection and pub/sub round-trip before enabling the adapter; configure sticky sessions and confirm with test connections across multiple instances; set up load balancing166. **Monitor** — Track connections, latency, throughput, error rates; add alerts for connection-count spikes and error-rate thresholds1718## Reference Guide1920Load detailed guidance based on context:2122| Topic | Reference | Load When |23|-------|-----------|-----------|24| Protocol | `references/protocol.md` | WebSocket handshake, frames, ping/pong, close codes |25| Scaling | `references/scaling.md` | Horizontal scaling, Redis pub/sub, sticky sessions |26| Patterns | `references/patterns.md` | Rooms, namespaces, broadcasting, acknowledgments |27| Security | `references/security.md` | Authentication, authorization, rate limiting, CORS |28| Alternatives | `references/alternatives.md` | SSE, long polling, when to choose WebSockets |2930## Code Examples3132### Server Setup (Socket.IO with Auth and Room Management)3334```js35import { createServer } from "http";36import { Server } from "socket.io";37import { createAdapter } from "@socket.io/redis-adapter";38import { createClient } from "redis";39import jwt from "jsonwebtoken";4041const httpServer = createServer();42const io = new Server(httpServer, {43 cors: { origin: process.env.ALLOWED_ORIGIN, credentials: true },44 pingTimeout: 20000,45 pingInterval: 25000,46});4748// Authentication middleware — runs before connection is established49io.use((socket, next) => {50 const token = socket.handshake.auth.token;51 if (!token) return next(new Error("Authentication required"));52 try {53 socket.data.user = jwt.verify(token, process.env.JWT_SECRET);54 next();55 } catch {56 next(new Error("Invalid token"));57 }58});5960// Redis adapter for horizontal scaling61const pubClient = createClient({ url: process.env.REDIS_URL });62const subClient = pubClient.duplicate();63await Promise.all([pubClient.connect(), subClient.connect()]);64io.adapter(createAdapter(pubClient, subClient));6566io.on("connection", (socket) => {67 const { userId } = socket.data.user;68 console.log(`connected: ${userId} (${socket.id})`);6970 // Presence: mark user online71 pubClient.hSet("presence", userId, socket.id);7273 socket.on("join-room", (roomId) => {74 socket.join(roomId);75 socket.to(roomId).emit("user-joined", { userId });76 });7778 socket.on("message", ({ roomId, text }) => {79 io.to(roomId).emit("message", { userId, text, ts: Date.now() });80 });8182 socket.on("disconnect", () => {83 pubClient.hDel("presence", userId);84 console.log(`disconnected: ${userId}`);85 });86});8788httpServer.listen(3000);89```9091### Client-Side Reconnection with Exponential Backoff9293```js94import { io } from "socket.io-client";9596const socket = io("wss://api.example.com", {97 auth: { token: getAuthToken() },98 reconnection: true,99 reconnectionAttempts: 10,100 reconnectionDelay: 1000, // initial delay (ms)101 reconnectionDelayMax: 30000, // cap at 30 s102 randomizationFactor: 0.5, // jitter to avoid thundering herd103});104105// Queue messages while disconnected106let messageQueue = [];107108socket.on("connect", () => {109 console.log("connected:", socket.id);110 // Flush queued messages111 messageQueue.forEach((msg) => socket.emit("message", msg));112 messageQueue = [];113});114115socket.on("disconnect", (reason) => {116 console.warn("disconnected:", reason);117 if (reason === "io server disconnect") socket.connect(); // manual reconnect118});119120socket.on("connect_error", (err) => {121 console.error("connection error:", err.message);122});123124function sendMessage(roomId, text) {125 const msg = { roomId, text };126 if (socket.connected) {127 socket.emit("message", msg);128 } else {129 messageQueue.push(msg); // buffer until reconnected130 }131}132```133134## Constraints135136### MUST DO137- Use sticky sessions for load balancing (WebSocket connections are stateful — requests must route to the same server instance)138- Implement heartbeat/ping-pong to detect dead connections (TCP keepalive alone is insufficient)139- Use rooms/namespaces for message scoping rather than filtering in application logic140- Queue messages during disconnection windows to avoid silent data loss141- Plan connection limits per instance before scaling horizontally142143### MUST NOT DO144- Store large state in memory without a clustering strategy (use Redis or an external store)145- Mix WebSocket and HTTP on the same port without explicit upgrade handling146- Forget to handle connection cleanup (presence records, room membership, in-flight timers)147- Skip load testing before production — connection-count spikes behave differently from HTTP traffic spikes148149## Output Templates150151When implementing WebSocket features, provide:1521. Server setup (Socket.IO/ws configuration)1532. Event handlers (connection, message, disconnect)1543. Client library (connection, events, reconnection)1554. Brief explanation of scaling strategy156157## Knowledge Reference158159Socket.IO, ws, uWebSockets.js, Redis adapter, sticky sessions, nginx WebSocket proxy, JWT over WebSocket, rooms/namespaces, acknowledgments, binary data, compression, heartbeat, backpressure, horizontal pod autoscaling