Node.js cookbook · Advanced
Scale real-time Socket.IO across instances
Broadcast chat messages to rooms when users are connected to different Node instances.
Approach
Use Socket.IO with the Redis adapter so io.to(room).emit() reaches sockets on every instance. Authenticate in a connection middleware, authorize room joins, validate and rate-limit incoming events, and enable sticky sessions at the load balancer if HTTP long-polling fallback is used.
Solution
import { createServer } from "node:http";
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
const httpServer = createServer(app);
const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);
const io = new Server(httpServer, { adapter: createAdapter(pub, sub), cors: { origin: "https://app.example.com", credentials: true } });
io.use(async (socket, next) => {
try {
socket.data.user = await verifyToken(socket.handshake.auth.token);
next();
} catch {
next(new Error("unauthorized"));
}
});
io.on("connection", (socket) => {
socket.on("join", async (roomId: string, ack) => {
if (!(await rooms.canJoin(socket.data.user.id, roomId))) return ack?.({ ok: false });
await socket.join(`room:${roomId}`);
ack?.({ ok: true });
});
socket.on("message", async ({ roomId, text }: { roomId: string; text: string }) => {
if (!socket.rooms.has(`room:${roomId}`) || typeof text !== "string" || text.length > 2000) return;
const message = await messages.save(roomId, socket.data.user.id, text);
io.to(`room:${roomId}`).emit("message", message);
});
});
httpServer.listen(3000);
Pitfalls
- No auth on the socket handshake.
- Trusting client-sent room IDs without authorization.
- Missing sticky sessions with polling transport across instances.
Variations
- Plain
ws+ Redis pub/sub for minimal overhead. - Managed realtime services for global scale.
Further reading: Socket.IO: Redis adapter
Reference: https://socket.io/docs/v4/redis-adapter/
More Node.js recipes
- Add health and readiness endpoints to a Node.js service
- Add JWT authentication to an Express API
- Cache expensive lookups in Redis from Node.js
- Call external APIs with timeouts and retries in Node.js
- Centralize error handling in Express 5
- Hash passwords and implement login safely in Node.js
- Implement cursor pagination with PostgreSQL in Node.js
- Offload CPU-heavy work with worker threads