DEV Community

ZNY
ZNY

Posted on

The Complete Guide to Building Real-Time Features with WebSockets in 2026

The Complete Guide to Building Real-Time Features with WebSockets in 2026

HTTP request-response is dead for modern apps. Whether it's collaborative editing, live dashboards, or gaming — users expect instant updates.

WebSocket vs HTTP Long-Polling vs SSE

Protocol Latency Browser Support Complexity
WebSocket ~0ms Excellent Medium
SSE ~100ms Excellent Low
Long-Polling ~500ms Universal Low
HTTP/2 Push ~0ms Limited Medium

WebSockets give you full-duplex, persistent connections.

Server Implementation (Node.js + ws)

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

// Room management
const rooms = new Map();

wss.on('connection', (ws, req) => {
  const url = new URL(req.url, 'http://localhost');
  const roomId = url.searchParams.get('room');
  const userId = url.searchParams.get('user');

  if (!rooms.has(roomId)) {
    rooms.set(roomId, new Map());
  }
  rooms.get(roomId).set(userId, ws);

  ws.roomId = roomId;
  ws.userId = userId;

  // Broadcast to room
  broadcast(roomId, {
    type: 'user_joined',
    userId,
    timestamp: Date.now()
  });

  ws.on('message', (data) => {
    const message = JSON.parse(data);
    broadcast(roomId, {
      type: 'chat',
      userId,
      content: message.content,
      timestamp: Date.now()
    });
  });

  ws.on('close', () => {
    rooms.get(roomId)?.delete(userId);
    broadcast(roomId, { type: 'user_left', userId });
  });
});

function broadcast(roomId, message) {
  const clients = rooms.get(roomId);
  if (!clients) return;
  const payload = JSON.stringify(message);
  for (const ws of clients.values()) {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(payload);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Scaling with Redis Pub/Sub

A single WebSocket server won't scale. Use Redis to broadcast across instances:

const redis = require('redis');
const redisSub = redis.createClient();
const redisPub = redis.createClient();

redisSub.subscribe('room:updates', (channel, message) => {
  const { roomId, data } = JSON.parse(message);
  broadcast(roomId, data);
});

function broadcast(roomId, message) {
  // Publish for other instances
  redisPub.publish('room:updates', JSON.stringify({ roomId, data: message }));
  // Also broadcast locally
  const clients = rooms.get(roomId);
  if (!clients) return;
  const payload = JSON.stringify(message);
  for (const ws of clients.values()) {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(payload);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Handling Disconnections Gracefully

WebSocket connections drop. Handle it:

// Heartbeat to detect dead connections
setInterval(() => {
  wss.clients.forEach((ws) => {
    if (ws.isAlive === false) {
      return ws.terminate();
    }
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('connection', (ws) => {
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });
});
Enter fullscreen mode Exit fullscreen mode

Security Considerations

  • Validate origins: Reject connections from unexpected origins
  • Authenticate on connect: Pass token in first message, reject if invalid
  • Rate limit messages: Prevent spam
  • Sanitize content: XSS in chat applications is serious

Conclusion

WebSockets enable real-time experiences that HTTP simply can't match. Start with a library like Socket.io for ease of use, scale with Redis when you need multiple servers, and always plan for disconnection.

Build your next real-time app on an all-in-one platform — includes WebSocket-ready hosting with auto-scaling.

Top comments (0)