Socket Io
Socket.IO patterns for event-driven real-time communication with automatic reconnection and room management
You are an expert in Socket.IO for building real-time applications with reliable, event-driven communication. ## Key Points - **Define a typed event contract** using TypeScript interfaces for both client and server to catch mismatches at compile time. - **Use rooms for group communication** instead of manually iterating over sockets. Rooms are optimized for broadcast. - **Apply middleware for auth** rather than checking credentials inside each event handler. - **Use acknowledgements for critical operations** where the sender needs confirmation (e.g., saving data, joining a room). - **Set `maxHttpBufferSize`** to limit payload size and prevent abuse. - **Prefer `socket.to(room)` over `io.in(room)`** when you want to exclude the sender from the broadcast. - **Not handling `connect_error`** — without this handler, auth failures and network issues are silently swallowed on the client. - **Assuming WebSocket transport** — Socket.IO may fall back to polling. Do not depend on a raw WebSocket connection existing. - **Storing state on socket objects** — socket instances are ephemeral. Use `socket.data` for per-connection metadata and an external store for persistent state. - **Ignoring the `disconnect` reason** — reasons like `"transport close"`, `"ping timeout"`, and `"server disconnect"` indicate very different problems. - **Not handling `connect_error` on the client** — without this handler, authentication failures and network issues are silently swallowed; the client appears to work but never connects. - **Confusing `io.emit`, `socket.emit`, `socket.to`, and `socket.broadcast`** — each has different targeting semantics; using the wrong one sends messages to too many or too few clients.
skilldb get websocket-skills/Socket IoFull skill: 248 linesSocket.IO — WebSockets & Real-Time
You are an expert in Socket.IO for building real-time applications with reliable, event-driven communication.
Overview
Socket.IO is a library that enables low-latency, bidirectional, event-based communication between a client and server. It builds on top of WebSockets but adds automatic reconnection, packet buffering, multiplexing via namespaces, room-based broadcasting, and a fallback to HTTP long-polling when WebSockets are unavailable.
Core Concepts
Transport Layer
Socket.IO uses the Engine.IO protocol under the hood. It starts with HTTP long-polling for reliability, then upgrades to WebSocket when possible. This two-phase approach ensures connectivity even through restrictive proxies and firewalls.
Namespaces
Namespaces are communication channels that split the logic of your application over a single shared connection. The default namespace is /. Custom namespaces let you separate concerns (e.g., /chat, /notifications).
Rooms
Rooms are arbitrary channels that sockets can join and leave. They exist only on the server side and are the primary mechanism for targeted broadcasting.
Events
Socket.IO uses a custom event system on top of the WebSocket message channel. Both client and server can emit named events with structured payloads and optional acknowledgement callbacks.
Implementation Patterns
Server Setup
import { Server } from 'socket.io';
import { createServer } from 'http';
import express from 'express';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: 'https://example.com',
methods: ['GET', 'POST'],
},
pingInterval: 25000,
pingTimeout: 20000,
maxHttpBufferSize: 1e6, // 1 MB
});
// Authentication middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = verifyJWT(token);
socket.data.user = user;
next();
} catch (err) {
next(new Error('Authentication failed'));
}
});
io.on('connection', (socket) => {
console.log(`User connected: ${socket.data.user.id}`);
socket.on('disconnect', (reason) => {
console.log(`User disconnected: ${reason}`);
});
});
httpServer.listen(3000);
Client Setup
import { io } from 'socket.io-client';
const socket = io('https://example.com', {
auth: { token: 'jwt-token-here' },
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 10,
});
socket.on('connect', () => {
console.log('Connected, id:', socket.id);
});
socket.on('connect_error', (err) => {
console.error('Connection error:', err.message);
});
Rooms and Broadcasting
// Server: room management
io.on('connection', (socket) => {
// Join a room
socket.on('join-room', (roomId) => {
socket.join(roomId);
socket.to(roomId).emit('user-joined', {
userId: socket.data.user.id,
name: socket.data.user.name,
});
});
// Leave a room
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left', { userId: socket.data.user.id });
});
// Broadcast to a room (excluding sender)
socket.on('room-message', ({ roomId, content }) => {
socket.to(roomId).emit('room-message', {
from: socket.data.user.id,
content,
timestamp: Date.now(),
});
});
// Broadcast to a room (including sender)
socket.on('room-event', ({ roomId, event }) => {
io.in(roomId).emit('room-event', event);
});
});
Acknowledgements
// Server
socket.on('save-draft', (data, callback) => {
try {
const result = saveDraft(data);
callback({ status: 'ok', id: result.id });
} catch (err) {
callback({ status: 'error', message: err.message });
}
});
// Client
socket.emit('save-draft', { title: 'My doc' }, (response) => {
if (response.status === 'ok') {
console.log('Saved with id:', response.id);
} else {
console.error('Save failed:', response.message);
}
});
// Client with timeout
socket.timeout(5000).emit('save-draft', { title: 'My doc' }, (err, response) => {
if (err) {
console.error('Server did not acknowledge in time');
}
});
Namespaces
// Server: separate namespace for admin
const adminNamespace = io.of('/admin');
adminNamespace.use((socket, next) => {
if (socket.data.user?.role !== 'admin') {
return next(new Error('Unauthorized'));
}
next();
});
adminNamespace.on('connection', (socket) => {
socket.on('get-stats', async (callback) => {
const stats = await getServerStats();
callback(stats);
});
});
// Client
const adminSocket = io('https://example.com/admin', {
auth: { token: adminToken },
});
Typed Events (TypeScript)
interface ServerToClientEvents {
'room-message': (msg: { from: string; content: string; timestamp: number }) => void;
'user-joined': (data: { userId: string; name: string }) => void;
}
interface ClientToServerEvents {
'join-room': (roomId: string) => void;
'room-message': (data: { roomId: string; content: string }) => void;
'save-draft': (data: { title: string }, cb: (res: { status: string; id?: string }) => void) => void;
}
interface SocketData {
user: { id: string; name: string; role: string };
}
const io = new Server<ClientToServerEvents, ServerToClientEvents, {}, SocketData>(httpServer);
Best Practices
- Define a typed event contract using TypeScript interfaces for both client and server to catch mismatches at compile time.
- Use rooms for group communication instead of manually iterating over sockets. Rooms are optimized for broadcast.
- Apply middleware for auth rather than checking credentials inside each event handler.
- Use acknowledgements for critical operations where the sender needs confirmation (e.g., saving data, joining a room).
- Set
maxHttpBufferSizeto limit payload size and prevent abuse. - Prefer
socket.to(room)overio.in(room)when you want to exclude the sender from the broadcast.
Common Pitfalls
- Not handling
connect_error— without this handler, auth failures and network issues are silently swallowed on the client. - Confusing
io.emit,socket.emit,socket.to, andsocket.broadcast— each has different targeting semantics.io.emitsends to all connected clients;socket.broadcast.emitsends to all except the sender. - Memory leaks from undisposed listeners — if you add listeners inside the
connectionhandler on the server, they are scoped to that socket. But on the client, callingsocket.onrepeatedly (e.g., in a ReactuseEffectwithout cleanup) leaks listeners. - Assuming WebSocket transport — Socket.IO may fall back to polling. Do not depend on a raw WebSocket connection existing.
- Storing state on socket objects — socket instances are ephemeral. Use
socket.datafor per-connection metadata and an external store for persistent state. - Ignoring the
disconnectreason — reasons like"transport close","ping timeout", and"server disconnect"indicate very different problems.
Core Philosophy
Socket.IO is not raw WebSockets — it is a protocol and framework built on top of WebSockets with reliability features that raw WebSockets lack. Automatic reconnection, packet buffering during disconnection, acknowledgements with timeouts, and room-based broadcasting are features you would otherwise implement yourself, poorly. Use Socket.IO when you need these reliability features, and use raw WebSockets when you need maximum control and minimal overhead.
Rooms are the primary abstraction for group communication. They are more efficient and less error-prone than manually iterating over socket lists. When you need to send a message to a subset of clients, put those clients in a room and broadcast to it. When you need to exclude the sender, use socket.to(room). When you need to include the sender, use io.in(room). This distinction is subtle but important — confusing them is a common source of bugs.
Type safety prevents the most common Socket.IO bugs: misspelled event names, wrong payload shapes, and forgotten callback types. Define TypeScript interfaces for ServerToClientEvents, ClientToServerEvents, and SocketData, and pass them as generics to the Server and Socket types. This catches mismatches at compile time rather than at runtime in production.
Anti-Patterns
-
Not handling
connect_erroron the client — without this handler, authentication failures and network issues are silently swallowed; the client appears to work but never connects. -
Confusing
io.emit,socket.emit,socket.to, andsocket.broadcast— each has different targeting semantics; using the wrong one sends messages to too many or too few clients. -
Adding event listeners without cleanup in React — calling
socket.oninside auseEffectwithout returning a cleanup function that callssocket.offleaks listeners on every re-render. -
Storing application state on socket objects — socket instances are ephemeral and disappear when the connection drops; use
socket.datafor per-connection metadata and an external store for persistent state. -
Assuming WebSocket transport is always used — Socket.IO may fall back to HTTP long-polling through restrictive proxies or firewalls; building features that depend on raw WebSocket behavior breaks in these environments.
Install this skill directly: skilldb add websocket-skills
Related Skills
Chat Rooms
Chat room architecture covering message routing, history, moderation, and scalable room-based real-time systems
Collaborative Editing
Real-time collaborative editing with CRDTs and Operational Transform for conflict-free concurrent document editing
Presence
User presence system design for tracking online/offline status, typing indicators, and activity in real-time apps
Reconnection
Reconnection and offline resilience patterns for WebSocket apps including retry strategies and state synchronization
Scaling Websockets
Scaling WebSocket applications with Redis pub/sub, sticky sessions, horizontal scaling, and load balancing strategies
Server Sent Events
Server-Sent Events (SSE) patterns for efficient unidirectional real-time streaming from server to client