Real-Time Web Applications: WebSockets, Server-Sent Events, and WebRTC Deep Dive

10 December 2024 · CodeMatic Team

Real-Time Web Applications

Real-time communication is essential for modern web applications. From live chat to collaborative editing, real-time features enhance user engagement and create immersive experiences. This guide covers WebSockets, Server-Sent Events, and WebRTC for building real-time applications.

WebSockets: Full-Duplex Communication

WebSockets provide full-duplex, bidirectional communication between client and server. Once established, the connection remains open, allowing instant message exchange.

Server-Side WebSocket Implementation (Node.js)

// Using ws library
import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });
const clients = new Set();

wss.on('connection', (ws, req) => {
  clients.add(ws);
  const clientId = req.url.split('?id=')[1];
  
  ws.on('message', (message) => {
    const data = JSON.parse(message.toString());
    
    // Broadcast to all clients except sender
    clients.forEach((client) => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          type: 'message',
          from: clientId,
          data,
          timestamp: Date.now(),
        }));
      }
    });
  });
  
  ws.on('close', () => {
    clients.delete(ws);
  });
  
  // Send welcome message
  ws.send(JSON.stringify({
    type: 'connected',
    message: 'Welcome to the chat!',
  }));
});

Client-Side WebSocket Implementation

// React hook for WebSocket
function useWebSocket(url: string) {
  const [socket, setSocket] = useState(null);
  const [messages, setMessages] = useState([]);
  const [isConnected, setIsConnected] = useState(false);
  
  useEffect(() => {
    const ws = new WebSocket(url);
    
    ws.onopen = () => {
      setIsConnected(true);
      console.log('WebSocket connected');
    };
    
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setMessages(prev => [...prev, data]);
    };
    
    ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
    
    ws.onclose = () => {
      setIsConnected(false);
      // Reconnect logic
      setTimeout(() => {
        setSocket(new WebSocket(url));
      }, 3000);
    };
    
    setSocket(ws);
    
    return () => {
      ws.close();
    };
  }, [url]);
  
  const sendMessage = (message: any) => {
    if (socket?.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify(message));
    }
  };
  
  return { messages, sendMessage, isConnected };
}

Server-Sent Events (SSE): Server-to-Client Streaming

SSE is simpler than WebSockets for one-way server-to-client communication. It's built on HTTP, making it easier to implement and debug.

Server-Side SSE Implementation

// Next.js API route
export async function GET(request: Request) {
  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    start(controller) {
      // Send initial connection message
      const data = `data: {"type":"connected"}\n\n`;
      controller.enqueue(encoder.encode(data));
      
      // Send updates every second
      const interval = setInterval(() => {
        const update = {
          type: 'update',
          data: { timestamp: Date.now(), value: Math.random() },
        };
        const message = `data: ${JSON.stringify(update)}\n\n`;
        controller.enqueue(encoder.encode(message));
      }, 1000);
      
      // Cleanup on close
      request.signal.addEventListener('abort', () => {
        clearInterval(interval);
        controller.close();
      });
    },
  });
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

Client-Side SSE Implementation

function useServerSentEvents(url: string) {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const eventSource = new EventSource(url);
    
    eventSource.onmessage = (event) => {
      const parsed = JSON.parse(event.data);
      setData(parsed);
    };
    
    eventSource.onerror = () => {
      eventSource.close();
      // Reconnect after delay
      setTimeout(() => {
        setData(null);
      }, 5000);
    };
    
    return () => {
      eventSource.close();
    };
  }, [url]);
  
  return data;
}

WebRTC: Peer-to-Peer Communication

WebRTC enables peer-to-peer audio, video, and data communication directly between browsers without intermediaries. Perfect for video conferencing, file sharing, or gaming.

WebRTC Connection Setup

// Signaling server (for ICE candidates and SDP exchange)
const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

// Handle ICE candidates
pc.onicecandidate = (event) => {
  if (event.candidate) {
    // Send to other peer via signaling server
    signaling.send({
      type: 'ice-candidate',
      candidate: event.candidate,
    });
  }
};

// Handle remote stream
pc.ontrack = (event) => {
  const remoteVideo = document.getElementById('remoteVideo');
  remoteVideo.srcObject = event.streams[0];
};

// Create offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', offer });

// Handle answer
signaling.on('answer', async ({ answer }) => {
  await pc.setRemoteDescription(answer);
});

// Add local stream
const localStream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true,
});
localStream.getTracks().forEach(track => {
  pc.addTrack(track, localStream);
});

Use Cases and When to Use Each

WebSockets - Best For:

  • Bidirectional real-time chat
  • Collaborative editing (Google Docs style)
  • Live updates and notifications
  • Gaming with real-time state sync
  • Any scenario requiring instant two-way communication

SSE - Best For:

  • Live feeds and updates (social media, news)
  • Progress bars and status updates
  • Real-time dashboards and metrics
  • Stock prices or live scores
  • When you only need server-to-client communication

WebRTC - Best For:

  • Video and audio conferencing
  • Peer-to-peer file sharing
  • Screen sharing
  • Low-latency gaming
  • Any direct peer-to-peer communication

Scaling Real-Time Applications

Horizontal Scaling with Redis Pub/Sub

// Multiple server instances sharing state via Redis
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);
const pub = new Redis(process.env.REDIS_URL);

wss.on('connection', (ws) => {
  // Subscribe to channel
  redis.subscribe('messages');
  
  ws.on('message', (message) => {
    // Publish to all servers
    pub.publish('messages', message.toString());
  });
});

redis.on('message', (channel, message) => {
  // Broadcast to all clients on this server
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
});

Error Handling and Reconnection

Implement robust error handling and automatic reconnection:

  • Exponential backoff for reconnection attempts
  • Connection state indicators in UI
  • Message queuing during disconnection
  • Heartbeat/ping mechanisms to detect dead connections
  • Graceful degradation when real-time fails

Performance Optimization

  • Compress messages (use binary format or compression)
  • Batch multiple updates into single messages
  • Throttle rapid updates on client side
  • Use message acknowledgments for reliability
  • Implement connection pooling and resource limits

Real-World Example

We built a real-time collaborative document editor:

  • WebSockets for bidirectional communication
  • Operational Transform (OT) for conflict resolution
  • Redis Pub/Sub for multi-server scaling
  • Message batching to reduce network overhead
  • Result: Sub-100ms latency, support for 1000+ concurrent users per document

Security Considerations

  • Authenticate connections before accepting
  • Validate all messages on server side
  • Rate limit connections and messages
  • Use WSS (WebSocket Secure) for encrypted connections
  • Implement proper CORS policies
  • Sanitize user input to prevent injection attacks

Conclusion

Real-time communication is essential for modern web applications. Choose WebSockets for bidirectional communication, SSE for server-to-client streaming, and WebRTC for peer-to-peer connections. Plan for scaling from the start, implement robust error handling, and prioritize security. Start simple and add complexity as needed.