Real-Time Communication: SSE vs WebSockets Explained with Express.js

Real-Time Communication: SSE vs WebSockets Explained with Express.js

By Mikey SharmaAug 18, 2025

Real-Time Communication: Event Streams vs. WebSockets (With Mermaid Diagrams & Express.js)

Modern applications like chat apps, live dashboards, and multiplayer games require real-time data transfer. Two key technologies enable this:

  1. Event Streams (SSE - Server-Sent Events) – Unidirectional (server → client) over HTTP.
  2. WebSockets – Full-duplex (client ↔ server) with persistent connections.

This guide explains both with Mermaid diagrams, Express.js examples, performance metrics, and comparisons.


1. Event Streams (SSE) – Simple Real-Time Updates

How SSE Works

SSE uses a long-lived HTTP connection where the server pushes updates to the client.

Loading diagram...

Key Features

Built-in reconnection (auto-retry if connection drops).
Text-based (UTF-8, no binary support).
Works over HTTP/1.1 and HTTP/2.
Unidirectional (server → client only).


Express.js SSE Example

Server-Side (Node.js + Express)

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

app.get('/sse', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Send data every second
  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: new Date().toISOString() })}\n\n`);
  }, 1000);

  req.on('close', () => clearInterval(interval));
});

app.listen(3000, () => console.log('SSE running on http://localhost:3000/sse'));

Client-Side (Browser JavaScript)

  const eventSource = new EventSource('http://localhost:3000/sse');
  
  eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log('Update:', data.time);
  };

  eventSource.onerror = () => {
    console.log('Connection lost. Reconnecting...');
  };

2. WebSockets – Full-Duplex Real-Time Communication

How WebSockets Work

  1. Handshake: Client sends an HTTP Upgrade: websocket request.
  2. Persistent Connection: After handshake, data flows both ways with minimal overhead.
Loading diagram...

Key Features

Bidirectional (client ↔ server).
Low latency (~1-50ms).
Supports binary & text data.
No auto-reconnect (must implement manually).


Express.js WebSocket Example (ws Library)

Server-Side (Express + WebSocket)

const express = require('express');
const WebSocket = require('ws');

const app = express();
const server = app.listen(3000);
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  console.log('New client connected');
  
  ws.on('message', (message) => {
    console.log('Received:', message.toString());
    ws.send(`Echo: ${message}`);
  });

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

Client-Side (Browser JavaScript)

 const socket = new WebSocket('ws://localhost:3000');

  socket.onopen = () => {
    console.log('Connected!');
    socket.send('Hello Server!');
  };

  socket.onmessage = (event) => {
    console.log('Server says:', event.data);
  };

  socket.onclose = () => {
    console.log('Disconnected. Reconnecting...');
    setTimeout(() => location.reload(), 1000); // Simple reconnect
  }

3. SSE vs. WebSockets – Comparison Table

FeatureSSE (Event Streams)WebSockets
DirectionServer → Client onlyBidirectional (Client ↔ Server)
ProtocolHTTP (text/event-stream)WebSocket (after HTTP upgrade)
ReconnectionAutomaticManual implementation needed
Data FormatText (UTF-8) onlyText + Binary
Latency~100-300ms (HTTP overhead)~1-50ms (low overhead)
Browser SupportAll except legacy IE/EdgeAll modern browsers
Best ForLive feeds, notificationsChat, gaming, real-time apps

4. When to Use Which?

Use SSE If:

✔ You only need server-to-client updates (e.g., stock prices, live tweets).
✔ You want simplicity (no extra libraries, just HTTP).
✔ You need auto-reconnect handling.

Use WebSockets If:

✔ You need two-way communication (e.g., chat, multiplayer games).
✔ You require lowest possible latency.
✔ You need binary data support (e.g., video streaming).


5. Advanced: Scaling & Performance Tips

For SSE:

  • Use HTTP/2 for better multiplexing.
  • Avoid blocking the main thread (use worker threads for heavy processing).

For WebSockets:

  • Enable permessage-deflate for compression.
  • Use Redis Pub/Sub for horizontal scaling.
Loading diagram...

6. Express.js Pro Tip: Use socket.io for Fallback Support

socket.io provides:
WebSocket + HTTP long-polling fallback (works even behind proxies).
Rooms & namespaces for complex apps.

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
  socket.emit('welcome', 'Connected to socket.io!');
  socket.on('chat', (msg) => {
    io.emit('message', msg); // Broadcast to all
  });
});

server.listen(3000);

7. References & Further Reading

📌 MDN: Server-Sent Events
📌 WebSocket API Docs
📌 ws Library (Lightweight WebSockets)
📌 Socket.io (Production-Grade WebSockets)


Final Verdict

  • SSE = Best for simple, server-pushed updates.
  • WebSockets = Best for interactive, low-latency apps.

Need help scaling? Let me know—I’ll cover load balancing & Redis integration next! 🚀

Share: