| name | websocket-design |
| description | Design WebSocket architecture for real-time communication. Outputs connection management, message protocols, scaling, and fallback strategies. |
| argument-hint | ["use case","concurrency","message frequency"] |
| allowed-tools | Read, Write, Bash |
WebSocket Architecture
Design production WebSocket system for real-time bidirectional communication. Not basic Socket.IO — connection lifecycle, message protocols, horizontal scaling, reconnection, and HTTP fallback.
Process
- Define use cases. Chat, live updates, collaborative editing, gaming.
- Choose protocol. Native WebSocket, Socket.IO (easier), GraphQL subscriptions.
- Design messages. JSON protocol with types, authentication, routing.
- Handle connections. Lifecycle (connect, disconnect, ping/pong), authentication.
- Scale horizontally. Redis pub/sub for multi-server, sticky sessions.
- Add reliability. Reconnection, message queuing, delivery guarantees.
- Monitor. Active connections, message rate, latency.
Output Format
WebSocket System: [Application Name]
Use Case: Real-time chat + notifications
Protocol: Socket.IO
Peak Connections: 100k concurrent
Scaling: Redis adapter (multi-server)
Fallback: Long polling (legacy browsers)
Protocol Comparison
| Feature | Native WebSocket | Socket.IO | GraphQL Subscriptions |
|---|
| Browser support | Modern only | All browsers | Modern only |
| Reconnection | Manual | Automatic | Automatic |
| Fallback | None | Long polling | Server-sent events |
| Rooms/namespaces | Manual | Built-in | Topic-based |
| Complexity | Low | Medium | High |
Recommendation: Socket.IO for reliability, native WebSocket for simplicity
Basic WebSocket (Node.js)
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
const clients = new Map();
wss.on('connection', (ws, req) => {
const clientId = generateId();
clients.set(clientId, { ws, userId: null });
console.log(`Client connected: ${clientId}`);
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
handleMessage(clientId, message);
} catch (err) {
ws.send(JSON.stringify({ error: 'Invalid JSON' }));
}
});
ws.on('close', () => {
console.log(`Client disconnected: ${clientId}`);
clients.(clientId);
});
interval = ( {
(ws. === ws.) {
ws.();
}
}, );
ws.(, (interval));
});
() {
client = clients.(clientId);
(message.) {
:
user = (message.);
(user) {
client. = user.;
client..(.({
: ,
: { : user., : user. }
}));
}
;
:
(message., clientId);
;
}
}
() {
packet = .({
: ,
: message,
senderId,
: .()
});
( [id, client] clients) {
(client.. === client..) {
client..(packet);
}
}
}
Socket.IO (Production-Ready)
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const io = new Server(3000, {
cors: { origin: '*' }
});
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
});
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = verifyJWT(token);
socket.userId = user.id;
socket.username = user.name;
next();
} catch (err) {
( ());
}
});
io.(, {
.();
socket.();
socket.(, (data) => {
message = {
: (),
: data.,
: socket.,
: socket.,
: .()
};
db..({ : message });
io.(data.).(, message);
});
socket.(, {
socket.(roomId);
socket.(, { roomId });
});
socket.(, {
socket.(roomId);
});
socket.(, {
socket.(roomId).(, {
: socket.,
: socket.
});
});
socket.(, {
.();
});
});
( {
io.(, {
: io...,
: .()
});
}, );
Message Protocol
interface ClientMessage {
type: 'auth' | 'subscribe' | 'unsubscribe' | 'message' | 'ping';
payload: any;
id?: string;
}
interface ServerMessage {
type: 'auth_success' | 'auth_error' | 'message' | 'error' | 'pong';
payload: any;
id?: string;
}
const authMessage: ClientMessage = {
type: 'auth',
payload: { token: 'jwt_token_here' }
};
const subscribeMessage: ClientMessage = {
type: 'subscribe',
payload: { channel: 'room:123' }
};
const chatMessage: ClientMessage = {
type: 'message',
payload: {
channel: 'room:123',
:
}
};
: = {
: ,
: { : , : }
};
: = {
: ,
: {
: ,
: ,
: ,
:
}
};
Client Implementation
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';
function useChatSocket(roomId, token) {
const [socket, setSocket] = useState(null);
const [messages, setMessages] = useState([]);
const [connected, setConnected] = useState(false);
useEffect(() => {
const newSocket = io('https://api.example.com', {
auth: { token },
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 5
});
newSocket.on('connect', () => {
console.log('Connected');
setConnected(true);
newSocket.emit('room:join', roomId);
});
newSocket.on('disconnect', (reason) => {
.(, reason);
();
});
newSocket.(, {
.(, err);
});
newSocket.(, {
( [...prev, message]);
});
(newSocket);
{
newSocket.();
};
}, [roomId, token]);
= () => {
(socket?.) {
socket.(, { content, roomId });
}
};
{ messages, sendMessage, connected };
}
() {
{ messages, sendMessage, connected } = (roomId, token);
(
);
}
Horizontal Scaling (Redis Adapter)
Problem: With multiple servers, Socket.IO clients on different servers can't communicate.
Solution: Redis pub/sub to sync messages across servers.
io.on('connection', (socket) => {
socket.on('message', (data) => {
io.to('room1').emit('message', data);
});
});
Redis channels:
socket.io#room1#/ → Messages to room1
socket.io#user:123#/ → Messages to specific user
Reconnection Strategy
const socket = io({
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000
});
socket.on('connect', () => {
console.log('Connected');
socket.emit('room:join', 'room1');
socket.emit('sync:messages', { since: lastMessageTimestamp });
});
socket.on('disconnect', (reason) => {
if (reason === 'io server disconnect') {
socket.connect();
}
});
Monitoring
import { Counter, Gauge, Histogram } from 'prom-client';
const connectionsGauge = new Gauge({
name: 'websocket_connections_active',
help: 'Active WebSocket connections'
});
const messagesCounter = new Counter({
name: 'websocket_messages_total',
help: 'Total messages sent',
labelNames: ['type']
});
const latencyHistogram = new Histogram({
name: 'websocket_message_latency_seconds',
help: 'Message latency'
});
io.on('connection', (socket) => {
connectionsGauge.inc();
socket.on('disconnect', () => {
connectionsGauge.dec();
});
socket.on('message', (data) => {
const start = Date.now();
messagesCounter.inc({ type: data.type });
latencyHistogram.observe((.() - start) / );
});
});
Metrics to track:
- Active connections
- Messages/second
- Message latency (p50, p95, p99)
- Reconnection rate
- Error rate
Security
Authentication
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('No token'));
}
jwt.verify(token, SECRET, (err, user) => {
if (err) return next(new Error('Invalid token'));
socket.userId = user.id;
next();
});
});
Rate Limiting
const rateLimits = new Map();
io.on('connection', (socket) => {
socket.on('message', (data) => {
const key = socket.userId;
const now = Date.now();
const limit = rateLimits.get(key) || { count: 0, resetAt: now + 60000 };
if (now > limit.resetAt) {
limit.count = 0;
limit.resetAt = now + 60000;
}
limit.count++;
rateLimits.set(key, limit);
if (limit.count > 100) {
socket.emit('error', { message: 'Rate limit exceeded' });
return;
}
});
});
Input Validation
socket.on('message', (data) => {
if (!data.content || typeof data.content !== 'string') {
return socket.emit('error', { message: 'Invalid message' });
}
if (data.content.length > 1000) {
return socket.emit('error', { message: 'Message too long' });
}
const clean = sanitizeHtml(data.content);
});
Rules
- Authentication required on connection — verify JWT before allowing communication.
- Redis adapter mandatory for multi-server deployments — enables cross-server messaging.
- Reconnection with exponential backoff — prevents thundering herd on server restart.
- Rate limiting per user/connection — prevents spam and abuse.
- Message size limits (1-10KB) — prevents memory exhaustion.
- Heartbeat/ping-pong every 30s — detects dead connections.
- Room-based broadcasting, not individual sends — scales to thousands of connections.
- Graceful shutdown: disconnect clients with warning — prevents abrupt connection loss.
- Message queuing during reconnect — prevent message loss.
- Monitor active connections, message rate, latency — alert on anomalies.