LatteStream®

Engineering

WebSocket Best Practices for Production Applications

Learn the essential best practices for implementing WebSockets in production, from connection management to scaling strategies.

LatteStream Team

October 22, 2025

6 min read

logo

WebSocket Best Practices for Production Applications

Building real-time applications with WebSockets can be challenging. After helping other companies implement WebSocket infrastructure, we've compiled the essential best practices that will help you build robust, scalable, and maintainable real-time applications.

Connection Management

1. Implement Automatic Reconnection

Network interruptions are inevitable, and WebSocket connections expect perfect conditions. Your WebSocket client should automatically attempt to reconnect when a connection is lost:

class ResilientWebSocket { constructor(url) { this.url = url; this.reconnectInterval = 1000; this.maxReconnectInterval = 30000; this.reconnectDecay = 1.5; this.reconnectAttempts = 0; this.connect(); } connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { console.log('Connected'); this.reconnectAttempts = 0; // Start up logic here }; this.ws.onclose = () => { // Add logic here if you have an intentional disconnect this.reconnect(); }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); }; } reconnect() { this.reconnectAttempts++; const timeout = Math.min( this.reconnectInterval * Math.pow(this.reconnectDecay, this.reconnectAttempts), this.maxReconnectInterval ); setTimeout(() => { console.log('Reconnecting...'); this.connect(); }, timeout); } }

2. Use Heartbeat/Ping-Pong

Keep connections alive and detect stale connections early. On low-bandwidth and spotty devices this is key to ensure WebSocket connection state is consistent:

class HeartbeatWebSocket { startHeartbeat() { this.pingInterval = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'ping' })); this.pongTimeout = setTimeout(() => { console.log('Connection timeout'); this.ws.close(); }, 5000); } }, 30000); } handleMessage(message) { const data = JSON.parse(message); if (data.type === 'pong') { clearTimeout(this.pongTimeout); } } }

Message Handling

3. Implement Message Queuing

Queue messages when the connection is unavailable or your sending messages on an interval:

class QueuedWebSocket { constructor() { this.messageQueue = []; this.isConnected = false; } send(message) { if (this.isConnected && this.ws.readyState === WebSocket.OPEN) { this.ws.send(message); } else { this.messageQueue.push(message); } } onConnect() { this.isConnected = true; // Flush message queue while (this.messageQueue.length > 0) { const message = this.messageQueue.shift(); this.ws.send(message); } } }

4. Use Message Acknowledgments

Ensure critical messages are delivered:

class AcknowledgedWebSocket { constructor() { this.pendingMessages = new Map(); this.messageId = 0; } sendWithAck(message, timeout = 5000) { return new Promise((resolve, reject) => { const id = ++this.messageId; const timeoutId = setTimeout(() => { this.pendingMessages.delete(id); reject(new Error('Message acknowledgment timeout')); }, timeout); this.pendingMessages.set(id, { resolve, timeoutId }); this.ws.send( JSON.stringify({ id, ...message, }) ); }); } handleAck(messageId) { const pending = this.pendingMessages.get(messageId); if (pending) { clearTimeout(pending.timeoutId); pending.resolve(); this.pendingMessages.delete(messageId); } } }

Security Best Practices

5. Authenticate Connections

Never trust unauthenticated WebSocket connections:

// Server-side authentication wss.on('connection', async (ws, req) => { const token = req.headers.authorization; try { const user = await verifyToken(token); ws.userId = user.id; ws.authenticated = true; } catch (error) { ws.close(1008, 'Invalid authentication'); return; } ws.on('message', (message) => { if (!ws.authenticated) { ws.close(1008, 'Not authenticated'); return; } // Process authenticated message }); });

6. Validate and Sanitize Input

Always validate incoming messages. While validating messages can also be emitted by type to various parts of the application:

const messageSchema = { type: 'object', properties: { type: { type: 'string', enum: ['chat', 'status', 'command'] }, payload: { type: 'object' }, timestamp: { type: 'number' }, }, required: ['type', 'payload'], additionalProperties: false, }; function validateMessage(message) { try { const data = JSON.parse(message); if (!ajv.validate(messageSchema, data)) { throw new Error('Invalid message format'); } return data; } catch (error) { console.error('Message validation failed:', error); return null; } }

Scaling Strategies

7. Use Connection Pooling

Limit the number of concurrent connections per client:

class ConnectionPool { constructor(maxConnections = 5) { this.connections = []; this.maxConnections = maxConnections; this.currentIndex = 0; } getConnection() { if (this.connections.length < this.maxConnections) { const ws = new WebSocket(this.url); this.connections.push(ws); return ws; } // Round-robin selection const connection = this.connections[this.currentIndex]; this.currentIndex = (this.currentIndex + 1) % this.connections.length; return connection; } }

8. Implement Rate Limiting

Protect your servers from abuse. Use windows to calculate allowed usage for more natural limits for WebSockets which can be bursty:

class RateLimiter { constructor(maxMessages = 100, windowMs = 60000) { this.maxMessages = maxMessages; this.windowMs = windowMs; this.clients = new Map(); } isAllowed(clientId) { const now = Date.now(); const client = this.clients.get(clientId) || { count: 0, resetTime: now + this.windowMs, }; if (now > client.resetTime) { client.count = 0; client.resetTime = now + this.windowMs; } if (client.count >= this.maxMessages) { return false; } client.count++; this.clients.set(clientId, client); return true; } }

Performance Optimization

9. Use Binary Messages When Appropriate

For high-frequency updates, consider using binary formats:

// Using msgpack for efficient serialization import msgpack from 'msgpack-lite'; function sendBinaryMessage(ws, data) { const packed = msgpack.encode(data); ws.send(packed); } ws.on('message', (data) => { if (data instanceof Buffer) { const unpacked = msgpack.decode(data); processMessage(unpacked); } else { // Handle text message processMessage(JSON.parse(data)); } });

10. Implement Message Batching

Reduce overhead by batching multiple messages. This is essential for queuing messages from disparate parts of the system:

class BatchedWebSocket { constructor() { this.batchQueue = []; this.batchInterval = 100; // ms this.startBatching(); } startBatching() { setInterval(() => { if (this.batchQueue.length > 0) { this.ws.send( JSON.stringify({ type: 'batch', messages: this.batchQueue, }) ); this.batchQueue = []; } }, this.batchInterval); } sendBatched(message) { this.batchQueue.push(message); } }

Monitoring and Debugging

11. Implement Comprehensive Logging

Track connection lifecycle and errors:

class MonitoredWebSocket { constructor() { this.metrics = { connectionsOpened: 0, connectionsClosed: 0, messagesReceived: 0, messagesSent: 0, errors: 0, }; } logMetrics() { console.log('WebSocket Metrics:', { ...this.metrics, activeConnections: this.metrics.connectionsOpened - this.metrics.connectionsClosed, timestamp: new Date().toISOString(), }); } setupMonitoring(ws) { ws.on('open', () => { this.metrics.connectionsOpened++; console.log('Connection opened', { timestamp: Date.now() }); }); ws.on('close', (code, reason) => { this.metrics.connectionsClosed++; console.log('Connection closed', { code, reason, timestamp: Date.now() }); }); ws.on('error', (error) => { this.metrics.errors++; console.error('WebSocket error', { error, timestamp: Date.now() }); }); } }

Conclusion

Implementing WebSockets in production requires careful consideration of connection management, security, scaling, and monitoring. By following these best practices, you'll build more reliable and scalable real-time applications.

Remember, while these practices provide a solid foundation, tools like LatteStream handle all of these complexities for you, allowing you to focus on building features rather than infrastructure.

Ready to implement these best practices without the hassle? Try LatteStream and get enterprise-grade WebSocket infrastructure in minutes.

LatteStream

WebSocket hosting that saves you a latte (seriously, start free).

© 2026 LatteStream, LLC. All rights reserved.

Made in California