Overview
WebSockets provide full-duplex, persistent communication between browsers and servers over a single TCP connection. Unlike HTTP polling, WebSockets enable real-time data flow with minimal latency, making them ideal for chat, live dashboards, multiplayer games, and collaborative editing.
When to Use
Use this resource when:
- Building chat applications or live comment systems. See Event-Driven Functions for backend event handling.
- Streaming real-time data to dashboards (stocks, metrics, IoT). See Prometheus API Monitoring for metrics dashboards.
- Implementing multiplayer game state synchronization. See Cold Start Optimization for low-latency serverless.
- Creating collaborative editing tools (like Google Docs). See JavaScript Event Loop for non-blocking UI updates.
Solution
Server with ws (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws, req) => {
clients.add(ws);
console.log('Client connected:', req.socket.remoteAddress);
ws.on('message', (data) => {
const message = JSON.parse(data);
// Broadcast to all connected clients
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'chat',
from: message.user,
text: message.text,
timestamp: Date.now()
}));
}
});
});
ws.on('close', () => clients.delete(ws));
ws.on('error', (err) => console.error('WebSocket error:', err));
});
Client Reconnection Logic
class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectInterval = 3000;
this.maxReconnectInterval = 30000;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
this.reconnectInterval = 3000; // Reset backoff
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.onMessage(data);
};
this.ws.onclose = () => {
console.log('Disconnected, reconnecting...');
setTimeout(() => this.connect(), this.reconnectInterval);
this.reconnectInterval = Math.min(
this.reconnectInterval * 2,
this.maxReconnectInterval
);
};
this.ws.onerror = (err) => console.error('WebSocket error:', err);
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
}
Explanation
WebSocket handshake upgrades an HTTP connection:
- Client sends upgrade request with Connection: Upgrade and Upgrade: websocket headers
- Server responds 101 Switching Protocols to confirm
- Bidirectional frames are exchanged over the persistent TCP socket
- Close handshake cleanly terminates the connection
Key differences from SSE:
- WebSockets are bidirectional; SSE is server-to-client only
- WebSockets use binary frames; SSE uses text/event-stream
- WebSockets need custom heartbeat/ping; SSE uses HTTP keep-alive
Variants
| Technology | Direction | Best For |
|---|---|---|
| WebSockets | Bidirectional | Chat, games, collaboration |
| SSE | Server-to-client | Live feeds, notifications |
| Long Polling | Server-to-client | Legacy browser support |
| MQTT over WebSocket | Pub/sub | IoT, telemetry |
What Works
- Implement heartbeat/ping: Detect dead connections with periodic ping/pong frames
- Authenticate during handshake: Pass JWT in query string or subprotocol
- Use rooms/channels: Do not broadcast everything to all clients
- Handle backpressure: Drop or queue messages if clients are slow
- Fallback to SSE: For clients behind strict proxies that block WebSockets
Common Mistakes
- No reconnection logic: Network hiccups permanently disconnect users
- Broadcasting to all clients: Scales poorly; use pub/sub or channel rooms
- Ignoring memory leaks: Closed connections not removed from client sets cause OOM
- Sending binary without framing: Always serialize structured data (JSON, Protobuf)
- Not handling proxy timeouts: Corporate proxies may kill idle connections after 30s
Variants and Alternatives
- WebSockets vs SSE vs long polling: WebSockets provide bidirectional communication with low latency. Server-Sent Events (SSE) are unidirectional (server to client) with automatic reconnection.
- WebSocket vs WebRTC: WebSockets are for client-server communication over TCP. WebRTC is for peer-to-peer communication over UDP with audio/video support.
- Socket.IO vs raw WebSockets vs WS: Socket. IO adds reconnection, rooms, and fallback to polling. Raw WebSockets are lighter but require manual handling. ws is a fast Node. js WebSocket library without Socket.
- Pub/Sub vs direct messaging: pub/sub decouples producers from consumers via channels/topics. Direct messaging sends to specific clients.
- WebSocket compression: per-message deflate (RFC 7692) compresses WebSocket frames. Reduces bandwidth by 50-80% for text-heavy payloads. Increases CPU usage.
- Message queue vs WebSocket stream: message queues (Redis, RabbitMQ) buffer messages for reliable delivery. WebSocket streams deliver in real-time but lose messages on disconnect.
Common Pitfalls in Production
- Connection leaks: unclosed WebSocket connections accumulate on the server. Set idle timeout.
- Memory pressure from connections: each WebSocket connection uses 20-100KB of memory. 10,000 connections use 200MB-1GB.
- Reconnection storms: when the server restarts, all clients reconnect simultaneously. ).
- Message ordering guarantees: WebSocket messages can arrive out of order on reconnection.
- Proxy and firewall issues: corporate proxies and firewalls may block WebSocket upgrades. Provide SSE or long polling fallback.
- Authentication on WebSocket: WebSocket connections do not support custom headers in browsers. Pass tokens via query parameter, subprotocol, or first message. Validate token on connection.
Integration Patterns
- Real-time chat architecture: client connects via WebSocket -> server authenticates -> joins room channel -> broadcasts messages to room members -> persists to database.
- Live data dashboard: server pushes updates via WebSocket -> client renders chart updates -> client buffers last N data points -> on disconnect, falls back to polling. g.
- Collaborative editing: client sends operations (not full document) -> server applies operations in order -> server broadcasts operations to other clients -> client applies remote operations.
- Notification system: server publishes events to Redis -> WebSocket workers subscribe to Redis -> workers push to connected clients -> clients show notifications.
- Multi-server WebSocket scaling: use a sticky session load balancer (nginx, HAProxy) or a shared state store (Redis). When a client connects to server A, other servers can reach it via Redis pub/sub.
- WebSocket gateway pattern: a gateway handles WebSocket connections and authentication. It forwards messages to backend services via HTTP or gRPC. Backend services push messages back via the gateway.
Tooling and Ecosystem
- Socket.IO: real-time library with reconnection, rooms, namespaces. 60K+ GitHub stars. Client and server libraries. Adapters for Redis, MongoDB, Postgres.
- ws: fast WebSocket library for Node. js. 21K+ GitHub stars. Minimal overhead. IO features. 2-3x faster than Socket.
- uWebSockets.js: ultra-fast WebSocket library for Node. js. C++ implementation. 10-20x faster than ws. Drop-in replacement for ws API.
- Redis Pub/Sub: in-memory pub/sub for multi-server WebSocket scaling. Sub-millisecond latency.
- Centrifugo: real-time messaging server. Supports WebSockets, SSE, HTTP-streaming. Built-in presence, history, and reconnection.
- Ably and Pusher: managed real-time messaging services.
Best Practices Summary
- Implement heartbeat/ping-pong to detect dead connections (every 30 seconds)
- Use jittered exponential backoff for reconnection (1s, 2s, 4s, 8s, max 30s)
- Authenticate WebSocket connections via token in query parameter or first message
- Use Redis pub/sub for multi-server WebSocket scaling
- Set idle timeout to close inactive connections (e.g., 5 minutes)
- Compress messages with per-message deflate for bandwidth-constrained clients
- Buffer and retransmit missed messages on reconnection using sequence numbers
- Monitor active connections, message rate, and memory usage
- Use SSE as a fallback when WebSockets are blocked by proxies
- Rate-limit messages per client to prevent abuse (e.g., 10 messages/second)
Error Handling and Recovery
- Connection drop recovery: when a WebSocket connection drops, the client should detect it within 30 seconds via heartbeat timeout. Immediately attempt reconnection with jittered backoff.
- Message delivery guarantees: for critical messages, implement an acknowledgment protocol. Client sends message with a unique ID -> server processes and sends ACK -> if no ACK within 5 seconds, client retries.
- Server crash recovery: use a shared state store (Redis) to persist connection metadata and buffered messages. When a new server instance starts, it reads from Redis and restores state.
- Backpressure handling: if a client is slow to process messages, the server should buffer up to N messages. If the buffer is full, drop non-critical messages or close the connection.
- Malformed message handling: validate message format on receipt. If invalid, log the error and ignore the message. Do not crash the WebSocket handler.
- Token expiration during connection: if the auth token expires mid-connection, the server should send a “token_expired” event. The client refreshes the token and sends a new “authenticate” message.
Performance Optimization Tips
- Use uWebSockets.js instead of ws for 10-20x better throughput in high-connection scenarios
- Enable per-message deflate compression to reduce bandwidth by 50-80% for text payloads
- Use binary frames instead of text frames for structured data. Binary is 20-30% smaller and faster to parse
- Implement message batching: buffer messages for 50ms and send as a single frame. Reduces overhead by 80% for high-frequency small messages
- Use a connection pool for Redis pub/sub. Each WebSocket server needs one Redis subscriber, not one per connection
- Set maxPayload to limit message size (e.g., 1MB). Prevents memory exhaustion from large messages
- Use ws.on(‘pong’, …) to track round-trip time. If RTT > 500ms, consider the connection degraded
- Monitor event loop lag. If lag > 100ms, the server is overloaded. Scale horizontally or optimize hot paths
- Use ws.terminate() instead of ws.close() for dead connections. erminate is immediate, close waits for a close frame
- Implement a connection rate limiter (e.g., max 10 new connections per second per IP) to prevent connection floods
Security Considerations
- Cross-site WebSocket hijacking (CSWSH): WebSocket connections do not enforce the same-origin policy by default. Validate the Origin header on the server. Reject connections from unknown origins.
- Authentication token security: pass auth tokens via the first message after connection, not via URL parameters (URLs are logged by proxies and servers).
- Message validation: validate every incoming message against a schema (JSON Schema, zod). Reject messages that do not match. Set a maximum message size (e. g. , 1MB). Rate-limit messages per connection (e. g. , 10 per second).
- Denial of service prevention: limit the number of concurrent connections per IP (e. g. , 10). Limit the total connections per server instance. Set idle timeout (e. g. , 5 minutes).
- TLS/WSS requirement: always use wss:// (WebSocket Secure) in production. Never use ws:// except for local development. TLS prevents eavesdropping and man-in-the-middle attacks.
- Data sanitization: sanitize all user-generated content before broadcasting to other clients. Strip HTML tags, escape special characters, and limit message length.
Testing and Quality Assurance
- Connection lifecycle testing: test connection, disconnection, and reconnection scenarios. Verify that heartbeat detects dead connections within 30 seconds.
- Load testing: use Artillery or k6 to simulate 10,000+ concurrent WebSocket connections.
- Message ordering tests: send messages with sequence numbers. Verify that the client receives them in order.
- Integration testing: test the full flow: client connects -> authenticates -> joins room -> sends message -> receives broadcast -> disconnects -> reconnects -> receives missed messages.
- Chaos testing: randomly kill server instances during active connections. Verify that clients reconnect to another instance. Verify that no messages are lost.
- Security testing: test for CSWSH by connecting from a different origin. Verify that the server rejects the connection. Verify that the server closes the connection.
Deployment and CI/CD
- WebSocket server deployment: deploy behind a reverse proxy (nginx, HAProxy) that supports WebSocket upgrade. Set appropriate timeouts (e. g.
- Horizontal scaling: use multiple WebSocket server instances behind a load balancer with sticky sessions. Use a shared presence store (Redis) for connection tracking.
- Zero-downtime deployment: deploy new instances alongside old instances. Drain old instances by sending a “server_shutting_down” event to connected clients. Clients reconnect to new instances.
- Connection draining on shutdown: on SIGTERM, stop accepting new connections. Send “reconnect” event to existing clients with a jittered delay (0-5 seconds). Wait for connections to close (max 30 seconds).
- Monitoring and alerting: monitor active connections, message rate, memory usage, and event loop lag. Set alerts for: connection count > 80% of max, message rate > 80% of capacity, event loop lag > 100ms, error rate > 1%.
- Health check endpoint: expose an HTTP endpoint (/health) that returns 200 if the WebSocket server is healthy. Check: Redis connectivity, memory usage < 80%, event loop lag < 50ms.
Cost Optimization
- Connection cost modeling: each WebSocket connection uses 20-100KB of server memory. 10,000 connections on a 2GB server costs ~/month. 100,000 connections require 10 servers at ~/month.
- Message volume cost: Redis pub/sub charges per message on managed services (Redis Cloud, AWS ElastiCache). Batch messages to reduce Redis operations.
- Managed WebSocket services: Ably charges per message and per connection. Pusher charges per connection and per event. For < 10,000 connections, managed is often cheaper (no devops overhead).
- Connection pooling for Redis: use a single Redis subscriber per server instance, not per WebSocket connection. This reduces Redis connections from 10,000 to 1 per server. edis.createClient() once at startup and share across all connections
- Auto-scaling based on connections: scale WebSocket servers based on active connection count. Scale up at 80% capacity. Scale down at 30% capacity.
- Bandwidth optimization: enable per-message deflate to reduce bandwidth by 50-80%.
Monitoring and Observability
- Connection metrics: track active connections, new connections per second, disconnections per second, and peak connections.
- Message metrics: track messages sent per second, messages received per second, average message size, and message error rate.
- Latency monitoring: measure round-trip time using ping/pong frames.
- Memory monitoring: track RSS, heap used, and heap total per WebSocket server instance.
- Event loop monitoring: track event loop lag using perf_hooks. monitorEventLoopDelay(). High lag indicates the server is overloaded.
- Distributed tracing for WebSocket: use OpenTelemetry to trace messages from client to server to Redis to another server to another client. This helps debug message delivery issues in multi-server setups.
Troubleshooting
- Component does not re-render: verify state reference, props, and memoization. A mutated object can bypass change detection.
- Style does not apply in production: check that CSS is loaded, class names are not mangled, and specificity wins. Purge unused styles carefully.
- Build fails after dependency update: read the changelog, pin versions, and clean the lock file.
- Accessibility audit fails: add labels, landmarks, focus management, and color contrast.
- Hydration mismatch: ensure server and client render the same initial HTML. random, or window during SSR.
Further Reading
- Official documentation: check the current reference for the framework or tool used.
- Related guides: explore the real-time and nodejs guides for deeper coverage.
- Complementary patterns: review design patterns applicable to your technology stack.
- Public postmortems: study real incidents from teams that faced similar production issues.
Production Notes
- Deploy gradually using canary or blue-green to catch regressions early.
- Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
- Document the rollback in the runbook; test the procedure in staging at least once per quarter.
- Review structured logs with correlation IDs to trace requests end-to-end during incidents.
Key Takeaways
- Apply websockets for real-time communication when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
Common Production Pitfalls
- Copying the example without adapting it to real data volumes and failure modes.
- Skipping load and error-injection tests before the first production deployment.
- Hard-coding values that should be configurable per environment.
- Forgetting to add logging and monitoring at each step.
- Deploying without a rollback plan or a tested backup strategy.
- Assuming the minimal example will scale without adding caching or batching.
- Not documenting the version and configuration used in production.
- Letting the recipe sit unchanged when dependencies or scale evolve.
Frequently Asked Questions
How many concurrent WebSocket connections can a server handle?
Node.js handles ~10k-50k connections per core. Use Redis pub/sub or a message bus to scale horizontally.
Can WebSockets work over HTTPS?
Yes — use wss:// (WebSocket Secure). Browsers block mixed-content ws:// on HTTPS pages.
What is the best fallback if WebSockets are blocked?
Server-Sent Events for server-to-client; HTTP long polling for bidirectional needs.
Related Resources
Server-Sent Events with Node.js and Express
Build server-to-client push with Server-Sent Events in Node.js and Express. Covers connections, heartbeats, reconnection, and safe broadcasting.
RecipeBuild a Bidirectional Chat with WebSocket and Node.js
How to build a real-time bidirectional chat application using WebSocket with room-based messaging, presence tracking, and message persistence
PatternMVC Pattern in Modern Frontend Applications
Apply the Model-View-Controller pattern to React and Vue applications to separate data, UI, and interaction logic for maintainable component architecture
RecipeExpress.js Middleware Composition Patterns
Build maintainable Express applications using middleware composition patterns for authentication, validation, error handling, request context propagation, and async route wrappers
RecipeURL Encoding and Decoding
Master URL encoding in JavaScript and other languages with encodeURI, encodeURIComponent, plus-safe handling, RFC 3986 compliance, and decoding edge cases
RecipeServer-Side Rendering
Improve performance and SEO with server-side rendering using Next.js, Nuxt, Astro, and other frameworks with hydration strategies.