Skip to content
Home
Real-Time Web Frameworks: WebSockets, SSE, and WebRTC

Real-Time Web Frameworks: WebSockets, SSE, and WebRTC

Backend Web Frameworks Backend Web Frameworks 8 min read 1597 words Beginner ExcellentWiki Editorial Team

Real-time web features — live chat, notifications, collaborative editing, gaming, and live dashboards — require bidirectional or server-initiated communication that traditional HTTP request-response cannot provide. Three main technologies enable real-time communication: WebSockets, Server-Sent Events (SSE), and WebRTC.

This guide covers each technology, when to use which, and how to scale real-time connections.

WebSocket vs SSE vs Long Polling

Real-time communication has three main approaches:

  • WebSockets provide full-duplex communication over a single TCP connection. The client and server can send messages independently at any time. Ideal for bidirectional applications like chat, gaming, and collaborative editing.
  • Server-Sent Events (SSE) provide one-way server-to-client streaming over standard HTTP. Simpler to implement and more firewall-friendly than WebSockets. Ideal for live scores, news feeds, and notification streams.
  • Long polling makes regular HTTP requests, with the server holding the response until new data is available. Works everywhere but has higher latency and overhead than WebSockets or SSE.

WebSockets

WebSockets start as an HTTP request that upgrades to the WebSocket protocol. Once established, both sides can send messages.

Server-Side (Node.js with ws)

import { WebSocketServer } from "ws";

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

wss.on("connection", (ws) => {
    console.log("Client connected");

    ws.on("message", (data) => {
        // Broadcast to all connected clients
        wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(data);
            }
        });
    });

    ws.on("close", () => {
        console.log("Client disconnected");
    });
---);

Client-Side

const ws = new WebSocket("ws://localhost:8080");

ws.onopen = () => {
    ws.send(JSON.stringify({ type: "join", room: "general" }));
---;

ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    displayMessage(message);
---;

ws.onclose = () => {
    console.log("Disconnected");
---;

Socket.IO

Socket.IO is the most popular WebSocket framework for Node.js. It provides fallback transports (long polling when WebSockets are unavailable), automatic reconnection, multiplexing (rooms and namespaces), and event-based messaging:

import { Server } from "socket.io";

const io = new Server(3000, {
    cors: { origin: "*" }
---);

io.on("connection", (socket) => {
    console.log(`User connected: ${socket.id}`);

    socket.on("join-room", (room) => {
        socket.join(room);
    });

    socket.on("chat-message", (data) => {
        io.to(data.room).emit("chat-message", {
            user: data.user,
            message: data.message,
            timestamp: Date.now()
        });
    });

    socket.on("disconnect", () => {
        console.log(`User disconnected: ${socket.id}`);
    });
---);

Socket.IO handles reconnection, buffering, and acknowledgment automatically. It is the recommended choice for most real-time applications due to its reliability and developer experience.

Server-Sent Events (SSE)

SSE streams text events from server to client over a single HTTP connection. The client cannot send data back over the same connection — use regular HTTP POST for client-to-server messages.

Server-Side (Python with FastAPI)

import asyncio
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse

app = FastAPI()

async def event_stream():
    while True:
        await asyncio.sleep(1)
        yield f"data: {json.dumps({'time': time.time()})}\n\n"

@app.get("/events")
async def stream_events(request: Request):
    return StreamingResponse(
        event_stream(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        }
    )

Client-Side

const eventSource = new EventSource("/events");

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    updateDashboard(data);
---;

eventSource.onerror = () => {
    console.log("Connection error, reconnecting...");
---;

SSE is simpler than WebSockets because it uses standard HTTP. It works through most proxies and firewalls without special configuration. The main limitation is unidirectional communication and connection limits (most browsers allow 6-8 concurrent HTTP connections per domain).

WebRTC

WebRTC enables peer-to-peer audio, video, and data transfer between browsers without a central server for media streaming. It uses a signaling server (usually WebSocket) for connection negotiation but streams data directly between peers.

Use WebRTC for video conferencing, peer-to-peer file sharing, and real-time multiplayer games where latency matters. The complexity is significantly higher than WebSockets or SSE due to NAT traversal, codec negotiation, and connection state management.

Scaling Real-Time Connections

Horizontal scaling of WebSocket connections requires additional infrastructure:

  • Sticky sessions — Load balancers route all requests from a client to the same server. Simple but limits resilience.
  • Pub/sub layer — Use Redis Pub/Sub or a message broker to broadcast messages across WebSocket servers. A server receives a message, publishes to Redis, and all servers receive and forward to their connected clients.
  • Adapters — Socket.IO has built-in Redis and MongoDB adapters for horizontal scaling. They handle message routing and session state across servers.
import { createAdapter } from "@socket.io/redis-adapter";
import Redis from "ioredis";

const pubClient = new Redis("redis://localhost:6379");
const subClient = pubClient.duplicate();

io.adapter(createAdapter(pubClient, subClient));

Measure concurrent connection limits for your framework and plan capacity accordingly. Socket.IO handles about 10K connections per Node.js instance. Elixir’s Phoenix Channels handle millions per node.

Summary

Choose WebSockets for bidirectional real-time communication. Use SSE for server-to-client streaming when you do not need client-to-server messages. Use WebRTC for peer-to-peer media. Socket.IO provides the best developer experience for WebSocket-based applications. Plan for horizontal scaling with a pub/sub layer from the start.

FAQ

What is the maximum number of concurrent WebSocket connections? It depends on your framework and server resources. Node.js with ws handles about 50K-100K connections per instance. Socket.IO handles about 10K-20K due to overhead. Phoenix (Elixir) handles millions. Plan to scale horizontally when approaching these limits.

When should I use SSE instead of WebSockets? Use SSE when you only need server-to-client data (notifications, live scores, feed updates) and want simplicity. SSE works over regular HTTP, through proxies, and does not require a protocol upgrade. The main limitation is unidirectional communication.

How do I handle reconnection after network interruption? Socket.IO handles this automatically with exponential backoff. For raw WebSockets, implement reconnection logic with increasing delays (1s, 2s, 4s, 8s… up to a max). Include a sequence number to detect missed messages.

Do I need sticky sessions for WebSockets? Yes, unless you use a pub/sub layer. Without sticky sessions, the load balancer may route a reconnecting client to a different server that does not have their session state. Redis Pub/Sub or a similar mechanism eliminates this dependency.

WebSocket vs SSE vs WebRTC

Choosing the right real-time protocol depends on the communication pattern:

WebSocket — Full-duplex, persistent connection between client and server. Both sides can send messages at any time. Ideal for chat applications, live collaboration, real-time gaming, and financial trading platforms. WebSocket has low overhead after the initial upgrade handshake. Libraries like Socket.IO and ws provide fallback mechanisms for environments where WebSocket is blocked.

Server-Sent Events (SSE) — Unidirectional server-to-client streaming over HTTP. The client subscribes to an event stream, and the server pushes updates. Simpler than WebSocket (uses standard HTTP), works through most proxies, and has automatic reconnection built in. Ideal for live feeds, notifications, status updates, and any scenario where only the server pushes data. Not suitable for bidirectional communication.

WebRTC — Peer-to-peer real-time communication for audio, video, and data channels. Signaling is handled through WebSocket or HTTP, but media streams flow directly between peers. Ideal for video conferencing, screen sharing, and peer-to-peer file transfer. Requires STUN/TURN servers for NAT traversal.

Connection Management

Real-time connections require careful resource management. Track connection state, implement heartbeats to detect stale connections, and set connection limits per client:

// WebSocket heartbeat example
const ws = new WebSocket("wss://example.com/live");
let heartbeat;

ws.onopen = () => {
    heartbeat = setInterval(() => ws.send(JSON.stringify({ type: "ping" })), 30000);
---;

ws.onclose = () => clearInterval(heartbeat);

// Server-side timeout detection
// If no pong received within 60 seconds, close the connection

Scaling Real-Time

Real-time connections are stateful — they cannot be load-balanced arbitrarily. Use Redis pub/sub to broadcast messages across server instances:

// Server A publishes
redis.publish("chat:messages", JSON.stringify(message));

// All servers subscribe
redis.subscribe("chat:messages", (channel, message) => {
    // Forward to connected WebSocket clients
---);

For horizontal scaling at extreme scale, consider dedicated real-time infrastructure like Pusher, Ably, or Firebase Realtime Database, which handle connection distribution and message broadcasting out of the box.

WebSocket Security

WebSocket connections have unique security considerations:

Origin validation — Check the Origin header during the WebSocket handshake to prevent cross-site WebSocket hijacking. Only allow origins that match your application’s domain.

Authentication — Authenticate during the HTTP handshake before upgrading to WebSocket. Do not accept unauthenticated WebSocket connections. Use cookies, JWTs in query parameters (with short expiration), or session tokens.

Authorization — Re-evaluate authorization when the connection upgrades. The user’s permissions may have changed since their initial authentication. Use per-message authorization for sensitive operations.

Rate limiting — WebSocket connections can send unlimited messages. Implement per-connection rate limiting to prevent abuse. Disconnect clients that exceed limits.

Graceful Shutdown

When a server shuts down, in-flight WebSocket connections must be drained gracefully:

  1. Stop accepting new connections
  2. Notify connected clients of impending shutdown (send a shutdown message)
  3. Wait for clients to acknowledge or timeout (30-60 seconds)
  4. Force close remaining connections
  5. Complete server shutdown

This prevents data loss and provides a better user experience than abrupt disconnection.

Real-Time Protocol Comparison

FeatureWebSocketSSEWebRTC
DirectionBidirectionalServer → ClientPeer-to-peer
Protocolws:// / wss://HTTP/HTTPSUDP (with TURN fallback)
Binary supportYesNo (text only)Yes
ReconnectionManualAutomaticManual
Browser supportUniversalUniversal (except IE)Modern browsers
Firewall friendlyModerate (upgrade)Yes (standard HTTP)No (UDP)
Use caseChat, gamingNotifications, feedsVideo, audio, P2P

Production Considerations

For production real-time systems at scale:

  • Use a message broker (Redis pub/sub, RabbitMQ, Kafka) to fan out messages across server instances
  • Implement client-side reconnection with exponential backoff and jitter
  • Monitor connection count, message throughput, and latency percentiles
  • Use health check endpoints to detect and restart unhealthy connections
  • Plan for graceful degradation when real-time is unavailable (fall back to polling)

Server-Sent Events Deep Dive

SSE is often overlooked but ideal for many use cases. The browser’s EventSource API handles reconnection automatically and tracks the last event ID for resuming after disconnection:

const events = new EventSource("/api/events");
events.addEventListener("message", (e) => {
    const data = JSON.parse(e.data);
    updateUI(data);
---);
events.addEventListener("error", () => {
    // EventSource auto-reconnects
---);

SSE works through HTTP proxies and CDNs, making it more deployment-friendly than WebSocket in restrictive network environments.


Related: WebSocket vs SSE (Comparative) | API Gateway Frameworks

Section: Backend Web Frameworks 1597 words 8 min read Beginner 756 articles in section Report inaccuracy Back to top