Production-ready patterns for real-time bidirectional communication based on RFC 6455 and Socket.io best practices.
Production-ready patterns for real-time bidirectional communication. Based on RFC 6455 (WebSocket Protocol), Socket.io v4 docs, and MDN WebSocket API.
Sources: RFC 6455, Socket.io Documentation, MDN Web Docs
States: CONNECTING (0), OPEN (1), CLOSING (2), CLOSED (3)
const io = new Server(server, {
pingTimeout: 60000, // Close if no pong after 60s
pingInterval: 25000, // Send ping every 25s
cors: { origin: process.env.CLIENT_URL }
});
io.on('connection', (socket) => {
socket.on('disconnect', (reason) => {
// 'transport close', 'client namespace disconnect', etc.
});
});
const socket = io('http://localhost:3000', {
reconnection: true,
reconnectionDelay: 1000, // Start at 1s
reconnectionDelayMax: 5000, // Max 5s
reconnectionAttempts: 5
});
socket.on('connect', () => {
// Rejoin rooms, sync state
});
// Server middleware (runs before connection event)
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
socket.data.user = jwt.verify(token, SECRET);
next();
} catch (err) {
next(new Error('Auth failed'));
}
});
// Client
const socket = io({ auth: { token: getToken() } });
// Join room
socket.join(roomId);
// Emit to room (including sender)
io.to(roomId).emit('event', data);
// Emit to room (excluding sender)
socket.to(roomId).emit('event', data);
// Server: callback = ack function
socket.on('send-msg', (msg, callback) => {
if (!msg.text) return callback({ error: 'Required' });
callback({ success: true, id: saved.id });
});
// Client: wait for ack
socket.emit('send-msg', msg, (response) => {
if (response.error) handleError();
});
io.emit('event', data); // All clients
socket.broadcast.emit('event', data); // All except sender
Socket.io handles automatically via pingInterval/pingTimeout. Native WebSocket:
// Client
const ws = new WebSocket('ws://localhost');
ws.addEventListener('open', () => {
setInterval(() => ws.send('ping'), 30000);
});
// Client
socket.on('connect_error', (err) => {
if (err.message === 'Auth failed') redirectLogin();
});
// Server
socket.on('error', (err) => {
logger.error({ socketId: socket.id, err });
});
socket.on('chat-msg', (msg) => {
const result = schema.safeParse(msg);
if (!result.success) return socket.emit('error', 'Invalid');
// Process
});
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
// Now events sync across all server instances
Use IP-based routing or session ID cookies to route client to same server instance.