$ -weight: 500;">npm -weight: 500;">install ws
-weight: 500;">npm -weight: 500;">install ws
-weight: 500;">npm -weight: 500;">install ws
// server.js
import { WebSocketServer } from 'ws';
import http from 'http'; const server = http.createServer();
const wss = new WebSocketServer({ server }); wss.on('connection', (ws, req) => { const ip = req.socket.remoteAddress; console.log(`Client connected: ${ip}`); ws.on('message', (data, isBinary) => { // Echo back to all connected clients wss.clients.forEach(client => { if (client.readyState === ws.OPEN) { client.send(data, { binary: isBinary }); } }); }); ws.on('close', (code, reason) => { console.log(`Client disconnected: ${code} ${reason}`); }); ws.on('error', (err) => { console.error('WebSocket error:', err); }); // Ping to detect dead connections ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; });
}); // Heartbeat interval — kills stale connections
const heartbeat = setInterval(() => { wss.clients.forEach(ws => { if (!ws.isAlive) return ws.terminate(); ws.isAlive = false; ws.ping(); });
}, 30_000); wss.on('close', () => clearInterval(heartbeat));
server.listen(3000);
// server.js
import { WebSocketServer } from 'ws';
import http from 'http'; const server = http.createServer();
const wss = new WebSocketServer({ server }); wss.on('connection', (ws, req) => { const ip = req.socket.remoteAddress; console.log(`Client connected: ${ip}`); ws.on('message', (data, isBinary) => { // Echo back to all connected clients wss.clients.forEach(client => { if (client.readyState === ws.OPEN) { client.send(data, { binary: isBinary }); } }); }); ws.on('close', (code, reason) => { console.log(`Client disconnected: ${code} ${reason}`); }); ws.on('error', (err) => { console.error('WebSocket error:', err); }); // Ping to detect dead connections ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; });
}); // Heartbeat interval — kills stale connections
const heartbeat = setInterval(() => { wss.clients.forEach(ws => { if (!ws.isAlive) return ws.terminate(); ws.isAlive = false; ws.ping(); });
}, 30_000); wss.on('close', () => clearInterval(heartbeat));
server.listen(3000);
// server.js
import { WebSocketServer } from 'ws';
import http from 'http'; const server = http.createServer();
const wss = new WebSocketServer({ server }); wss.on('connection', (ws, req) => { const ip = req.socket.remoteAddress; console.log(`Client connected: ${ip}`); ws.on('message', (data, isBinary) => { // Echo back to all connected clients wss.clients.forEach(client => { if (client.readyState === ws.OPEN) { client.send(data, { binary: isBinary }); } }); }); ws.on('close', (code, reason) => { console.log(`Client disconnected: ${code} ${reason}`); }); ws.on('error', (err) => { console.error('WebSocket error:', err); }); // Ping to detect dead connections ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; });
}); // Heartbeat interval — kills stale connections
const heartbeat = setInterval(() => { wss.clients.forEach(ws => { if (!ws.isAlive) return ws.terminate(); ws.isAlive = false; ws.ping(); });
}, 30_000); wss.on('close', () => clearInterval(heartbeat));
server.listen(3000);
-weight: 500;">npm -weight: 500;">install socket.io
-weight: 500;">npm -weight: 500;">install socket.io
-weight: 500;">npm -weight: 500;">install socket.io
// server.js
import { createServer } from 'http';
import { Server } from 'socket.io'; const httpServer = createServer();
const io = new Server(httpServer, { cors: { origin: 'https://yourdomain.com', methods: ['GET', 'POST'] }, transports: ['websocket', 'polling'], // WebSocket first, polling fallback pingTimeout: 20_000, pingInterval: 25_000
}); io.on('connection', (socket) => { console.log(`Connected: ${socket.id} from ${socket.handshake.address}`); // Join a room socket.join(`user:${socket.handshake.auth.userId}`); // Listen for events socket.on('chat:message', async (msg, callback) => { // Validate, persist, then broadcast await saveMessage(msg); io.to(`room:${msg.roomId}`).emit('chat:message', msg); callback({ -weight: 500;">status: 'delivered' }); // acknowledgement }); socket.on('disconnect', (reason) => { console.log(`Disconnected: ${socket.id} reason=${reason}`); });
}); httpServer.listen(3000);
// server.js
import { createServer } from 'http';
import { Server } from 'socket.io'; const httpServer = createServer();
const io = new Server(httpServer, { cors: { origin: 'https://yourdomain.com', methods: ['GET', 'POST'] }, transports: ['websocket', 'polling'], // WebSocket first, polling fallback pingTimeout: 20_000, pingInterval: 25_000
}); io.on('connection', (socket) => { console.log(`Connected: ${socket.id} from ${socket.handshake.address}`); // Join a room socket.join(`user:${socket.handshake.auth.userId}`); // Listen for events socket.on('chat:message', async (msg, callback) => { // Validate, persist, then broadcast await saveMessage(msg); io.to(`room:${msg.roomId}`).emit('chat:message', msg); callback({ -weight: 500;">status: 'delivered' }); // acknowledgement }); socket.on('disconnect', (reason) => { console.log(`Disconnected: ${socket.id} reason=${reason}`); });
}); httpServer.listen(3000);
// server.js
import { createServer } from 'http';
import { Server } from 'socket.io'; const httpServer = createServer();
const io = new Server(httpServer, { cors: { origin: 'https://yourdomain.com', methods: ['GET', 'POST'] }, transports: ['websocket', 'polling'], // WebSocket first, polling fallback pingTimeout: 20_000, pingInterval: 25_000
}); io.on('connection', (socket) => { console.log(`Connected: ${socket.id} from ${socket.handshake.address}`); // Join a room socket.join(`user:${socket.handshake.auth.userId}`); // Listen for events socket.on('chat:message', async (msg, callback) => { // Validate, persist, then broadcast await saveMessage(msg); io.to(`room:${msg.roomId}`).emit('chat:message', msg); callback({ -weight: 500;">status: 'delivered' }); // acknowledgement }); socket.on('disconnect', (reason) => { console.log(`Disconnected: ${socket.id} reason=${reason}`); });
}); httpServer.listen(3000);
// client.js — runs in browser
class ReconnectingWebSocket { constructor(url) { this.url = url; this.ws = null; this.reconnectDelay = 1000; this.maxDelay = 30_000; this.shouldReconnect = true; this.connect(); } connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log('Connected'); this.reconnectDelay = 1000; // reset on successful connect }; this.ws.onmessage = (event) => { this.onMessage?.(JSON.parse(event.data)); }; this.ws.onclose = (event) => { if (!this.shouldReconnect) return; console.log(`Disconnected (${event.code}). Reconnecting in ${this.reconnectDelay}ms`); setTimeout(() => this.connect(), this.reconnectDelay); // Exponential backoff with jitter this.reconnectDelay = Math.min( this.reconnectDelay * 2 + Math.random() * 1000, this.maxDelay ); }; this.ws.onerror = (err) => { console.error('WebSocket error', err); this.ws.close(); }; } send(data) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } } close() { this.shouldReconnect = false; this.ws?.close(); }
}
// client.js — runs in browser
class ReconnectingWebSocket { constructor(url) { this.url = url; this.ws = null; this.reconnectDelay = 1000; this.maxDelay = 30_000; this.shouldReconnect = true; this.connect(); } connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log('Connected'); this.reconnectDelay = 1000; // reset on successful connect }; this.ws.onmessage = (event) => { this.onMessage?.(JSON.parse(event.data)); }; this.ws.onclose = (event) => { if (!this.shouldReconnect) return; console.log(`Disconnected (${event.code}). Reconnecting in ${this.reconnectDelay}ms`); setTimeout(() => this.connect(), this.reconnectDelay); // Exponential backoff with jitter this.reconnectDelay = Math.min( this.reconnectDelay * 2 + Math.random() * 1000, this.maxDelay ); }; this.ws.onerror = (err) => { console.error('WebSocket error', err); this.ws.close(); }; } send(data) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } } close() { this.shouldReconnect = false; this.ws?.close(); }
}
// client.js — runs in browser
class ReconnectingWebSocket { constructor(url) { this.url = url; this.ws = null; this.reconnectDelay = 1000; this.maxDelay = 30_000; this.shouldReconnect = true; this.connect(); } connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log('Connected'); this.reconnectDelay = 1000; // reset on successful connect }; this.ws.onmessage = (event) => { this.onMessage?.(JSON.parse(event.data)); }; this.ws.onclose = (event) => { if (!this.shouldReconnect) return; console.log(`Disconnected (${event.code}). Reconnecting in ${this.reconnectDelay}ms`); setTimeout(() => this.connect(), this.reconnectDelay); // Exponential backoff with jitter this.reconnectDelay = Math.min( this.reconnectDelay * 2 + Math.random() * 1000, this.maxDelay ); }; this.ws.onerror = (err) => { console.error('WebSocket error', err); this.ws.close(); }; } send(data) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } } close() { this.shouldReconnect = false; this.ws?.close(); }
}
-weight: 500;">npm -weight: 500;">install @socket.io/redis-adapter ioredis
-weight: 500;">npm -weight: 500;">install @socket.io/redis-adapter ioredis
-weight: 500;">npm -weight: 500;">install @socket.io/redis-adapter ioredis
import { createServer } from 'http';
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis'; const pubClient = new Redis({ host: 'redis', port: 6379 });
const subClient = pubClient.duplicate(); const io = new Server(createServer(), { adapter: createAdapter(pubClient, subClient)
}); // Now `io.to('room:123').emit(...)` works across all server instances
io.on('connection', socket => { socket.join(`user:${socket.handshake.auth.userId}`);
});
import { createServer } from 'http';
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis'; const pubClient = new Redis({ host: 'redis', port: 6379 });
const subClient = pubClient.duplicate(); const io = new Server(createServer(), { adapter: createAdapter(pubClient, subClient)
}); // Now `io.to('room:123').emit(...)` works across all server instances
io.on('connection', socket => { socket.join(`user:${socket.handshake.auth.userId}`);
});
import { createServer } from 'http';
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis'; const pubClient = new Redis({ host: 'redis', port: 6379 });
const subClient = pubClient.duplicate(); const io = new Server(createServer(), { adapter: createAdapter(pubClient, subClient)
}); // Now `io.to('room:123').emit(...)` works across all server instances
io.on('connection', socket => { socket.join(`user:${socket.handshake.auth.userId}`);
});
import { Redis } from 'ioredis'; const pub = new Redis();
const sub = new Redis();
const localClients = new Map(); // socketId -> ws sub.subscribe('broadcast', (err) => { if (err) console.error('Subscribe failed', err);
}); sub.on('message', (channel, message) => { const { targetId, data } = JSON.parse(message); const client = localClients.get(targetId); if (client?.readyState === 1) { client.send(data); }
}); // When a message needs to reach any server:
async function sendToUser(userId, data) { await pub.publish('broadcast', JSON.stringify({ targetId: userId, data }));
}
import { Redis } from 'ioredis'; const pub = new Redis();
const sub = new Redis();
const localClients = new Map(); // socketId -> ws sub.subscribe('broadcast', (err) => { if (err) console.error('Subscribe failed', err);
}); sub.on('message', (channel, message) => { const { targetId, data } = JSON.parse(message); const client = localClients.get(targetId); if (client?.readyState === 1) { client.send(data); }
}); // When a message needs to reach any server:
async function sendToUser(userId, data) { await pub.publish('broadcast', JSON.stringify({ targetId: userId, data }));
}
import { Redis } from 'ioredis'; const pub = new Redis();
const sub = new Redis();
const localClients = new Map(); // socketId -> ws sub.subscribe('broadcast', (err) => { if (err) console.error('Subscribe failed', err);
}); sub.on('message', (channel, message) => { const { targetId, data } = JSON.parse(message); const client = localClients.get(targetId); if (client?.readyState === 1) { client.send(data); }
}); // When a message needs to reach any server:
async function sendToUser(userId, data) { await pub.publish('broadcast', JSON.stringify({ targetId: userId, data }));
}
// Check bufferedAmount before sending large payloads
function safeSend(ws, data) { const MAX_BUFFER = 1024 * 1024; // 1MB if (ws.bufferedAmount > MAX_BUFFER) { console.warn(`Client ${ws.id} buffer full — dropping message`); return false; } ws.send(data); return true;
}
// Check bufferedAmount before sending large payloads
function safeSend(ws, data) { const MAX_BUFFER = 1024 * 1024; // 1MB if (ws.bufferedAmount > MAX_BUFFER) { console.warn(`Client ${ws.id} buffer full — dropping message`); return false; } ws.send(data); return true;
}
// Check bufferedAmount before sending large payloads
function safeSend(ws, data) { const MAX_BUFFER = 1024 * 1024; // 1MB if (ws.bufferedAmount > MAX_BUFFER) { console.warn(`Client ${ws.id} buffer full — dropping message`); return false; } ws.send(data); return true;
}
ws.on('drain', () => { // Socket has drained — safe to resume sending stream.resume();
});
ws.on('drain', () => { // Socket has drained — safe to resume sending stream.resume();
});
ws.on('drain', () => { // Socket has drained — safe to resume sending stream.resume();
});
// Socket.IO — middleware runs before connection is established
io.use(async (socket, next) => { const token = socket.handshake.auth.token; if (!token) return next(new Error('Unauthorized')); try { const payload = await verifyJWT(token); socket.data.userId = payload.sub; next(); } catch { next(new Error('Invalid token')); }
});
// Socket.IO — middleware runs before connection is established
io.use(async (socket, next) => { const token = socket.handshake.auth.token; if (!token) return next(new Error('Unauthorized')); try { const payload = await verifyJWT(token); socket.data.userId = payload.sub; next(); } catch { next(new Error('Invalid token')); }
});
// Socket.IO — middleware runs before connection is established
io.use(async (socket, next) => { const token = socket.handshake.auth.token; if (!token) return next(new Error('Unauthorized')); try { const payload = await verifyJWT(token); socket.data.userId = payload.sub; next(); } catch { next(new Error('Invalid token')); }
});
// Track with Prometheus or your preferred metrics library
const metrics = { connections_total: 0, connections_active: 0, messages_received_total: 0, messages_sent_total: 0, errors_total: 0
}; wss.on('connection', (ws) => { metrics.connections_total++; metrics.connections_active++; ws.on('message', () => metrics.messages_received_total++); ws.on('close', () => metrics.connections_active--); ws.on('error', () => metrics.errors_total++);
}); // Expose /metrics endpoint for Prometheus scrape
// Track with Prometheus or your preferred metrics library
const metrics = { connections_total: 0, connections_active: 0, messages_received_total: 0, messages_sent_total: 0, errors_total: 0
}; wss.on('connection', (ws) => { metrics.connections_total++; metrics.connections_active++; ws.on('message', () => metrics.messages_received_total++); ws.on('close', () => metrics.connections_active--); ws.on('error', () => metrics.errors_total++);
}); // Expose /metrics endpoint for Prometheus scrape
// Track with Prometheus or your preferred metrics library
const metrics = { connections_total: 0, connections_active: 0, messages_received_total: 0, messages_sent_total: 0, errors_total: 0
}; wss.on('connection', (ws) => { metrics.connections_total++; metrics.connections_active++; ws.on('message', () => metrics.messages_received_total++); ws.on('close', () => metrics.connections_active--); ws.on('error', () => metrics.errors_total++);
}); // Expose /metrics endpoint for Prometheus scrape - You control both client and server (pure Node.js environment)
- You need maximum performance and minimum overhead
- You're building a custom protocol on top of WebSocket
- Binary data or streams are first-class concerns - Automatic fallback to HTTP long-polling (for environments that block WS)
- Rooms and namespaces — broadcast to groups without managing sets manually
- Built-in reconnection logic on the client
- Redis adapter for multi-server scaling (first-class, not bolted-on)
- Acknowledgements — request/response pattern over WebSocket - You need browser support in enterprise/restricted environments (long-polling fallback)
- You're building room-based features (chat, collaboration, gaming lobbies)
- You want built-in reconnection handled client-side
- You need to scale horizontally and want an off-the-shelf adapter - Reset delay on success — once connected, -weight: 500;">start the backoff from scratch
- Jitter — the Math.random() * 1000 prevents a thundering herd when a server restarts and 10,000 clients try to reconnect simultaneously at the exact same time
- shouldReconnect flag — allows intentional disconnects without triggering reconnection - Origin validation: Check req.headers.origin against your allowlist
- Rate limiting: Limit messages per second per connection using a token bucket
- Message size limits: ws option maxPayload: 100 * 1024 caps messages at 100KB
- TLS: WebSockets over plain TCP (ws://) are unencrypted — always use wss:// in production - connections_active spike (possible DDoS or traffic event)
- errors_total rate (connection instability or TLS issues)
- Memory RSS crossing 80% of container limit (backpressure or leak) - [ ] Heartbeat/ping-pong — detect and terminate dead connections every 30s
- [ ] Reconnection with jitter — prevent thundering herd on server -weight: 500;">restart
- [ ] Redis adapter — required for any multi-server deployment
- [ ] TLS termination — terminate wss:// at load balancer (nginx/Caddy), forward plain WS internally
- [ ] JWT auth middleware — validate on handshake, not per-message
- [ ] Message size cap — maxPayload in ws, maxHttpBufferSize in Socket.IO
- [ ] Rate limiting — token bucket per socket
- [ ] Memory leak testing — use --inspect + Chrome DevTools heap snapshots under load
- [ ] Graceful shutdown — close all connections with code 1001 (going away) before process exit
- [ ] Metrics — connections active, messages/sec, error rate