WebSocket API Development for Web Application
WebSocket — protocol providing two-way communication channel between client and server over single TCP connection. Suitable for: real-time chat, live notifications, collaborative editing, stock quotes, online games.
When WebSocket Needed
| Task | WebSocket | Polling | SSE |
|---|---|---|---|
| Chat, collaboration | ✓ | Bad | No (one-directional) |
| Notifications | ✓ | Acceptable | ✓ |
| Quotes/analytics | ✓ | Bad | ✓ |
| File upload progress | No need | No need | ✓ |
| REST CRUD | No need | — | — |
Basic Implementation (Node.js + ws)
import { WebSocketServer } from 'ws';
import { createServer } from 'http';
const server = createServer(app);
const wss = new WebSocketServer({ server });
// Store connections: room → Set<WebSocket>
const rooms = new Map<string, Set<WebSocket>>();
wss.on('connection', (ws, req) => {
const roomId = new URL(req.url!, 'http://x').searchParams.get('room');
if (!roomId) return ws.close(4000, 'Missing room');
// Add to room
if (!rooms.has(roomId)) rooms.set(roomId, new Set());
rooms.get(roomId)!.add(ws);
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
// Broadcast to room
rooms.get(roomId)?.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
});
ws.on('close', () => {
rooms.get(roomId)?.delete(ws);
});
});
Message Protocol
Structured JSON protocol instead of raw text:
// Message types
type WSMessage =
| { type: 'join_room'; roomId: string; userId: string }
| { type: 'message'; roomId: string; text: string; timestamp: number }
| { type: 'typing'; roomId: string; userId: string }
| { type: 'error'; code: string; message: string };
Authentication
WebSocket doesn't support custom headers during handshake. Options:
-
Token in query string —
ws://api.com/ws?token=eyJ...(visible in logs, less secure) -
Cookie — when ws:// on same domain,
withCredentials: true - First message auth — first message after connection — authentication
ws.on('connection', (socket) => {
let authenticated = false;
const authTimeout = setTimeout(() => {
if (!authenticated) socket.close(4001, 'Auth timeout');
}, 5000);
socket.once('message', (data) => {
const { type, token } = JSON.parse(data.toString());
if (type === 'auth' && validateToken(token)) {
authenticated = true;
clearTimeout(authTimeout);
socket.send(JSON.stringify({ type: 'auth_success' }));
} else {
socket.close(4001, 'Invalid token');
}
});
});
Horizontal Scaling
Problem: with multiple servers, client A connected to server 1, client B to server 2. Message from A won't reach B.
Solution — Redis Pub/Sub for inter-server communication:
import { createClient } from 'redis';
const pub = createClient();
const sub = createClient();
// Server receives message from client
ws.on('message', async (data) => {
await pub.publish(`room:${roomId}`, data.toString());
});
// All servers subscribed and deliver to their clients
sub.subscribe(`room:${roomId}`, (message) => {
rooms.get(roomId)?.forEach(client => {
if (client.readyState === WebSocket.OPEN) client.send(message);
});
});
Socket.io vs Native WebSocket
Socket.io — wrapper over WebSocket with long polling fallback, automatic reconnection, rooms, namespaces and acks:
io.to(roomId).emit('message', { text: 'Hello' }); // instead of manual broadcast
Native WebSocket — less overhead, full control, no extra abstractions. Recommended for > 10K connections or when latency critical.
Heartbeat and Reconnect
Browsers and proxies close idle connections. Ping/pong every 30 seconds:
wss.on('connection', (ws) => {
let alive = true;
ws.on('pong', () => { alive = true; });
const interval = setInterval(() => {
if (!alive) return ws.terminate();
alive = false;
ws.ping();
}, 30000);
ws.on('close', () => clearInterval(interval));
});
Timeline
WebSocket server with rooms, authentication, Redis Pub/Sub: 1–2 weeks. With Socket.io, message protocol, reconnect handling and tests: 2–3 weeks.







