Back to Blog
API architecture
API integration
API management

Real-time APIs: WebSockets vs Server-Sent Events

4 min read
J
John
Senior API Architect

Real-time APIs: WebSockets vs Server-Sent Events

Real-time data streaming is fundamental to modern applications, from live notifications to collaborative editing and financial tickers. Two dominant technologies for real-time communication are WebSockets and Server-Sent Events (SSE). Both serve distinct purposes and understanding their implementation nuances is critical for selecting the right tool.

WebSockets: Full-Duplex Real-Time Communication

WebSockets provide a persistent, bidirectional communication channel over a single TCP connection. Once established, both client and server can send messages independently, making it ideal for interactive applications.

Establishing a WebSocket Connection

The WebSocket protocol begins with an HTTP-based handshake. The client sends an upgrade request:

GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

The server responds with:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Client-Side Implementation

Modern browsers provide the WebSocket API:

const socket = new WebSocket('wss://api.example.com/ws');

socket.onopen = () => {
  socket.send(JSON.stringify({type: 'subscribe', channel: 'updates'}));
};

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updateUI(data);
};

socket.onerror = (error) => {
  console.error('WebSocket error:', error);
};

socket.onclose = () => {
  console.log('Connection closed');
};

Server-Side Implementation with Node.js

Using the popular ws library:

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

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    const data = JSON.parse(message);
    
    // Process message and potentially broadcast
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          type: 'update',
          payload: processedData
        }));
      }
    });
  });

  // Send initial data
  ws.send(JSON.stringify({type: 'connected', timestamp: Date.now()}));
});

Advanced WebSocket Considerations

Production implementations require:

  • Heartbeat mechanisms to detect dead connections
  • Message compression (permessage-deflate extension)
  • Authentication during handshake
  • Connection scaling with Redis Pub/Sub for multiple servers

Server-Sent Events: Efficient Server-to-Client Streaming

SSE provides a unidirectional channel from server to client over standard HTTP. It's simpler to implement than WebSockets and benefits from existing HTTP infrastructure.

Establishing an SSE Connection

The client initiates a persistent HTTP connection:

const eventSource = new EventSource('/api/events');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  handleUpdate(data);
};

eventSource.addEventListener('customEvent', (event) => {
  const data = JSON.parse(event.data);
  handleCustomEvent(data);
});

eventSource.onerror = () => {
  // Handle reconnection automatically
};

Server-Side Implementation

Using Express.js with proper headers:

const express = require('express');
const app = express();

app.get('/api/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('Access-Control-Allow-Origin', '*');
  
  // Send initial data
  res.write(`data: ${JSON.stringify({status: 'connected'})}\n\n`);
  
  // Send updates every second
  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({update: new Date().toISOString()})}\n\n`);
  }, 1000);
  
  // Cleanup on client disconnect
  req.on('close', () => {
    clearInterval(interval);
  });
});

SSE Message Format

SSE uses a specific text format:

event: notification
data: {"id": 123, "message": "Update available"}
id: 12345
retry: 3000\n\n
  • event: Custom event type
  • data: Message content (multiple lines supported)
  • id: Message identifier for replay protection
  • retry: Reconnection time in milliseconds

Practical Comparison and Implementation Scenarios

Connection Management

WebSockets maintain a stateful connection requiring explicit reconnection logic. SSE automatically reconnects on failure with configurable retry intervals.

Protocol Overhead

WebSockets have minimal framing overhead (2-14 bytes per message). SSE uses HTTP chunked encoding with slightly higher overhead but benefits from HTTP/2 multiplexing.

Data Formats

WebSockets support binary and text data natively:

// Binary data
socket.send(new ArrayBuffer(16));

// Text data
socket.send(JSON.stringify({binary: false}));

SSE is text-only, requiring Base64 encoding for binary data.

Scalability Considerations

WebSockets require stateful servers or sticky sessions in load-balanced environments. SSE works with standard load balancers but requires connection tracking for targeted messaging.

Security Implementation

Both protocols inherit HTTP security models. Use WSS (WebSocket Secure) and HTTPS exclusively in production.

Authentication Example

WebSocket authentication during handshake:

// Client-side with token
const socket = new WebSocket('wss://api.example.com/ws', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

// Server-side verification
wss.on('connection', (ws, req) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!verifyToken(token)) {
    ws.close(1008, 'Unauthorized');
  }
});

SSE authentication with cookies or query parameters:

// Client-side
const eventSource = new EventSource('/api/events?token=' + encodeURIComponent(token));

// Server-side
app.get('/api/events', (req, res) => {
  if (!verifyToken(req.query.token)) {
    return res.status(401).end();
  }
  // Proceed with SSE setup
});

Performance and Browser Support

WebSockets offer lower latency for bidirectional communication. SSE provides better efficiency for server-to-client streaming with automatic reconnection.

Both technologies enjoy excellent browser support, though WebSockets have slightly broader implementation across all modern browsers and platforms.

Implement proper monitoring for both technologies, tracking connection stability, message throughput, and error rates. Use protocol-specific metrics: WebSocket frame processing times and SSE reconnection frequency provide valuable insights into system health.

Back to Blog