| name | websockets |
| description | WebSocket implementation for real-time bidirectional communication. Use when user mentions "websocket", "ws://", "wss://", "real-time", "live updates", "chat application", "socket.io", "Server-Sent Events", "SSE", "push notifications", "live data", "streaming data", "bidirectional communication", "websocket server", "reconnection", or building real-time features. |
WebSocket Reference
Protocol Basics
WebSocket upgrades an HTTP connection to a persistent, full-duplex channel over a single TCP connection.
Handshake: Client sends an HTTP Upgrade request; server responds with 101 Switching Protocols.
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Frame opcodes:
0x0 continuation frame
0x1 text frame (UTF-8)
0x2 binary frame
0x8 connection close
0x9 ping
0xA pong
Close codes: 1000 normal, 1001 going away, 1006 abnormal (no close frame), 1008 policy violation, 1011 server error, 1012 service restart, 1013 try again later.
Server: Node.js (ws)
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws, req) => {
const ip = req.socket.remoteAddress;
console.log(`Client connected from ${ip}`);
ws.on('message', (data, isBinary) => {
const msg = isBinary ? data : data.toString();
wss.clients.forEach(client => {
if (client !== ws && client.readyState === 1) {
client.send(msg, { binary: isBinary });
}
});
});
ws.on('close', (code, reason) => {
console.log(`Disconnected: ${code} ${reason}`);
});
ws.on('error', (err) => console.error('WS error:', err));
ws.send(JSON.stringify({ type: 'welcome', timestamp: Date.now() }));
});
const interval = setInterval(() => {
wss.clients.forEach(ws => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('connection', ws => {
ws.isAlive = true;
ws.on('pong', () => { ws.isAlive = true; });
});
wss.on('close', () => clearInterval(interval));
Attach to existing HTTP server
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import express from 'express';
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
wss.on('connection', (ws) => { });
server.listen(3000);
Server: Python (websockets)
import asyncio
import websockets
import json
connected = set()
async def handler(websocket):
connected.add(websocket)
try:
async for message in websocket:
data = json.loads(message)
websockets.broadcast(connected, json.dumps({
"user": data.get("user"),
"text": data.get("text"),
}))
finally:
connected.discard(websocket)
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
await asyncio.Future()
asyncio.run(main())
Server: Go (gorilla/websocket)
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
for {
msgType, msg, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
break
}
if err := conn.WriteMessage(msgType, msg); err != nil {
log.Println("Write error:", err)
break
}
}
}
func main() {
http.HandleFunc("/ws", wsHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Client-Side JavaScript
const ws = new WebSocket('wss://example.com/ws');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onerror = (err) => console.error('WebSocket error:', err);
ws.onclose = (event) => {
console.log(`Closed: code=${event.code} reason=${event.reason} clean=${event.wasClean}`);
};
ws.send('plain text message');
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.(, );
ws.(buffer);
(ws. === .) {
ws.();
}
Binary data handling
ws.binaryType = 'arraybuffer';
ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
const view = new DataView(event.data);
const value = view.getFloat64(0);
console.log('Binary value:', value);
} else {
console.log('Text:', event.data);
}
};
const blob = new Blob(['binary content'], { type: 'application/octet-stream' });
ws.send(blob);
const floats = new Float32Array([1.0, 2.0, 3.0]);
ws.send(floats.buffer);
Socket.IO
Server
import { Server } from 'socket.io';
import { createServer } from 'http';
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: 'https://example.com', methods: ['GET', 'POST'] },
pingInterval: 25000,
pingTimeout: 20000,
});
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
socket.user = verifyToken(token);
next();
} catch (err) {
next(new Error('Authentication failed'));
}
});
const adminNs = io.of('/admin');
adminNs.use(adminAuthMiddleware);
adminNs.on('connection', (socket) => { });
io.on(, {
.();
socket.();
socket.(, {
io.(msg.).(, {
: socket..,
: msg.,
: .(),
});
({ : });
});
socket.(, {
socket.(room);
socket.(room).(, socket..);
});
socket.(, {
socket.(room);
socket.(room).(, socket..);
});
socket.(, {
.();
});
});
httpServer.();
Client
import { io } from 'socket.io-client';
const socket = io('wss://example.com', {
auth: { token: 'jwt-token-here' },
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: 10,
});
socket.on('connect', () => console.log('Connected:', socket.id));
socket.emit('chat:message', { room: 'general', text: 'Hello' }, (response) => {
console.log('Server ack:', response.status);
});
socket.on('chat:message', (msg) => {
console.log(`${msg.user}: ${msg.text}`);
});
socket.on('connect_error', (err) => {
console.error(, err.);
});
Authentication Patterns
Token in query params (simple, visible in logs)
const ws = new WebSocket(`wss://example.com/ws?token=${jwt}`);
Server-side validation:
wss.on('connection', (ws, req) => {
const url = new URL(req.url, 'http://localhost');
const token = url.searchParams.get('token');
if (!verifyToken(token)) {
ws.close(1008, 'Invalid token');
return;
}
});
Cookie-based (leverages existing session)
const ws = new WebSocket('wss://example.com/ws');
First-message authentication
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'auth', token: jwt }));
};
wss.on('connection', (ws) => {
ws.authenticated = false;
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (!ws.authenticated) {
if (msg.type === 'auth' && verifyToken(msg.token)) {
ws.authenticated = true;
ws.send(JSON.stringify({ type: 'auth', status: 'ok' }));
} else {
ws.close(1008, 'Authentication required');
}
return;
}
handleMessage(ws, msg);
});
});
Reconnection with Exponential Backoff
function createReconnectingWS(url, options = {}) {
const { maxRetries = 10, baseDelay = 1000, maxDelay = 30000 } = options;
let retries = 0;
let ws;
function connect() {
ws = new WebSocket(url);
ws.onopen = () => {
retries = 0;
console.log('Connected');
};
ws.onclose = (event) => {
if (event.code === 1000) return;
if (retries < maxRetries) {
const delay = Math.min(baseDelay * 2 ** retries + Math.random() * 1000, maxDelay);
console.log(`Reconnecting in ${Math.round(delay)}ms (attempt ${retries + 1})`);
setTimeout(connect, delay);
retries++;
} else {
console.error('Max reconnection attempts reached');
}
};
ws. = {};
ws;
}
();
}
Client-side heartbeat
function startHeartbeat(ws, intervalMs = 30000) {
const timer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, intervalMs);
ws.addEventListener('close', () => clearInterval(timer));
return timer;
}
Scaling WebSockets
Redis adapter for Socket.IO (horizontal scaling)
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
const io = new Server(httpServer);
io.adapter(createAdapter(pubClient, subClient));
io.emit('global:event', { data: 'reaches everyone' });
Sticky sessions with nginx
upstream websocket_servers {
ip_hash; # sticky sessions based on client IP
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
listen 443 ssl;
server_name ws.example.com;
location /ws {
proxy_pass http://websocket_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s; # prevent nginx from closing idle connections
proxy_send_timeout 86400s;
}
}
Message Patterns
Pub/Sub with channels
const channels = new Map();
wss.on('connection', (ws) => {
ws.subscriptions = new Set();
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'subscribe') {
if (!channels.has(msg.channel)) channels.set(msg.channel, new Set());
channels.get(msg.channel).add(ws);
ws.subscriptions.add(msg.channel);
}
if (msg.type === 'publish') {
const subs = channels.get(msg.channel);
if (subs) {
const payload = JSON.stringify({ channel: msg.channel, data: msg.data });
subs.forEach(client => {
(client. === ) client.(payload);
});
}
}
});
ws.(, {
ws..( {
channels.(ch)?.(ws);
(channels.(ch)?. === ) channels.(ch);
});
});
});
Request/Response (correlation IDs)
let msgId = 0;
const pending = new Map();
function request(ws, method, params) {
return new Promise((resolve, reject) => {
const id = ++msgId;
pending.set(id, { resolve, reject, timer: setTimeout(() => {
pending.delete(id);
reject(new Error('Request timeout'));
}, 10000) });
ws.send(JSON.stringify({ id, method, params }));
});
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.id && pending.has(msg.id)) {
const { resolve, timer } = pending.get(msg.id);
clearTimeout(timer);
pending.delete(msg.id);
resolve(msg.result);
}
};
const user = await (ws, , { : });
Server-Sent Events (SSE) as Alternative
Use SSE when you only need server-to-client push. Simpler than WebSocket, works through proxies, auto-reconnects.
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const sendEvent = (event, data) => {
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
};
sendEvent('connected', { status: 'ok' });
const interval = setInterval(() => {
sendEvent('heartbeat', { time: Date.now() });
}, 15000);
req.on('close', () => clearInterval(interval));
});
const source = new EventSource('/events');
source.addEventListener('connected', (e) => console.log(JSON.parse(e.)));
source. = .();
source.();
When to use SSE vs WebSocket:
- SSE: notifications, live feeds, dashboards, log tailing -- server-to-client only
- WebSocket: chat, gaming, collaborative editing -- bidirectional required
Rate Limiting WebSocket Connections
wss.on('connection', (ws) => {
let messageCount = 0;
let lastReset = Date.now();
const MAX_MESSAGES_PER_SECOND = 10;
ws.on('message', (data) => {
const now = Date.now();
if (now - lastReset > 1000) {
messageCount = 0;
lastReset = now;
}
messageCount++;
if (messageCount > MAX_MESSAGES_PER_SECOND) {
ws.send(JSON.stringify({ error: 'Rate limit exceeded' }));
return;
}
handleMessage(ws, data);
});
});
const connectionCounts = new Map();
wss.on('connection', (ws, req) => {
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
const count = (connectionCounts.get(ip) || 0) + ;
(count > ) {
ws.(, );
;
}
connectionCounts.(ip, count);
ws.(, {
connectionCounts.(ip, (connectionCounts.(ip) || ) - );
});
});
Testing WebSocket Endpoints
wscat -c ws://localhost:8080
wscat -c wss://example.com/ws -H "Authorization: Bearer token"
websocat ws://localhost:8080
echo '{"type":"ping"}' | websocat ws://localhost:8080
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
http://localhost:8080/ws
Automated testing (Node.js)
import { WebSocket } from 'ws';
import { test } from 'node:test';
import assert from 'node:assert';
test('echo server returns sent message', async () => {
const ws = new WebSocket('ws://localhost:8080');
const reply = await new Promise((resolve, reject) => {
ws.on('open', () => ws.send('hello'));
ws.on('message', (data) => resolve(data.toString()));
ws.on('error', reject);
setTimeout(() => reject(new Error('Timeout')), 5000);
});
assert.strictEqual(reply, 'hello');
ws.close();
});
Common Architectures
Chat application
Client A โโwsโโ> Server โโwsโโ> Client B
โ
โโโ Room management (join/leave)
โโโ Message persistence (DB)
โโโ Presence tracking (online/offline)
โโโ Typing indicators (ephemeral broadcast)
Live dashboard
Data Source โโ> Server โโwsโโ> Dashboard Clients
โ
โโโ Aggregate/throttle updates (100ms batching)
โโโ Send diffs, not full state
โโโ Client reconnects with last-seen timestamp
Collaborative editing
Client A โโwsโโ> Server โโwsโโ> Client B
โ
โโโ Operational Transform (OT) or CRDT
โโโ Version vector for conflict resolution
โโโ Cursor position broadcasting
Notification system
Backend Service โโRedis pub/subโโ> WS Server โโwsโโ> Clients
โ
โโโ Per-user channels
โโโ Unread count sync
โโโ Fallback to polling if WS unavailable
Error Handling Best Practices
wss.on('connection', (ws) => {
ws.on('message', (data) => {
let msg;
try {
msg = JSON.parse(data);
} catch {
ws.send(JSON.stringify({ error: 'Invalid JSON' }));
return;
}
if (!msg.type) {
ws.send(JSON.stringify({ error: 'Missing message type' }));
return;
}
try {
handleMessage(ws, msg);
} catch (err) {
console.error('Handler error:', err);
ws.send(JSON.stringify({ error: 'Internal server error' }));
}
});
ws.on('error', (err) => {
console.error('Socket error:', err.message);
});
});
process.(, {
wss..( ws.(, ));
wss.( process.());
});