cd ..
sse

Server-Sent Events: The Simple Alternative to WebSockets

Learn when to use SSE instead of WebSockets for one-way real-time streaming, with Node.js code examples

3 min read

When building real-time features like notifications, live dashboards, or activity feeds, most developers immediately reach for WebSockets. But there's a simpler alternative that works perfectly for one-way server-to-client communication: Server-Sent Events (SSE).

Why SSE?

WebSockets are powerful—they give you full-duplex, bidirectional communication over a single connection. But that power comes with complexity:

  • Protocol upgrade required
  • Special port considerations
  • Connection management overhead
  • Binary data handling

SSE solves this by staying strictly within HTTP. No upgrades, no special ports, no headache. Just plain old HTTP connections that stay open for streaming data from server to client.

When to Use SSE vs WebSockets

ScenarioUse SSEUse WebSocket
Server pushing notifications
Live dashboard updates
Activity feeds
Chat applications
Multiplayer games
Real-time collaboration

Use SSE when: You only need the server to send data to the client. Notifications, dashboards, live feeds, progress bars, and log streaming are perfect use cases.

Use WebSockets when: You need bidirectional communication where both client and server send messages freely.

How SSE Works

SSE uses a simple text-based wire format. The server keeps an HTTP connection open and writes events as plain text:

data: {"message": "hello"}
 
data: {"message": "world"}

Each event can have:

  • data - The payload
  • event - Optional event type (for filtering)
  • id - Event ID for resumption on reconnect
  • retry - Reconnection timeout in milliseconds

Server Implementation (Node.js/Express)

Here's a simple SSE endpoint:

const express = require('express');
const cors = require('cors');
 
const app = express();
app.use(cors());
 
const clients = [];
 
app.get('/events', (req, res) => {
  const headers = {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  };
  
  res.writeHead(200, headers);
 
  const clientId = Date.now();
  const newClient = {
    id: clientId,
    res,
  };
  
  clients.push(newClient);
 
  req.on('close', () => {
    console.log(`${clientId} Connection closed`);
    clients = clients.filter(c => c.id !== clientId);
  });
});
 
// Endpoint to broadcast events to all connected clients
app.post('/broadcast', express.json(), (req, res) => {
  const { message } = req.body;
  
  clients.forEach(client => {
    client.res.write(`data: ${JSON.stringify({ message })}\n\n`);
  });
  
  res.json({ success: true });
});
 
app.listen(3000, () => {
  console.log('SSE server running on port 3000');
});

Client Implementation

The browser provides a native EventSource API:

const eventSource = new EventSource('http://localhost:3000/events');
 
eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data.message);
});
 
eventSource.addEventListener('open', () => {
  console.log('Connected to SSE stream');
});
 
eventSource.addEventListener('error', (event) => {
  if (event.readyState === EventSource.CLOSED) {
    console.log('Connection closed');
  }
});
 
// Clean up when done
// eventSource.close();

Key Features

Automatic Reconnection

The browser automatically reconnects if the connection drops. No extra code needed.

Event IDs for Resumption

// Server sends events with IDs
client.res.write(`id: 1\ndata: {"step": 1}\n\n`);
client.res.write(`id: 2\ndata: {"step": 2}\n\n`);
 
// On reconnect, browser sends Last-Event-ID header
// Server can resume from where client left off

Event Types

// Server: send typed events
client.res.write(`event: notification\ndata: {"type": "alert"}\n\n`);
client.res.write(`event: update\ndata: {"status": "complete"}\n\n`);
 
// Client: listen for specific types
eventSource.addEventListener('notification', (e) => {
  console.log('Notification:', e.data);
});

Production Considerations

Scaling

SSE connections are stateful. For horizontal scaling:

  • Use a message broker (Redis pub/sub, Kafka, NATS)
  • Each server subscribes to the broker and relays to connected clients
  • Consider sticky sessions or proxy-level routing

Security

  • Always use HTTPS
  • Authenticate at connection time (query params, headers)
  • Implement rate limiting per connection
  • Set reasonable retry intervals

Monitoring

Track:

  • Active connection count
  • Connection open/close rates
  • Message throughput and latency
  • Error rates

Conclusion

Server-Sent Events are the forgotten gem of real-time web development. When you only need server-to-client streaming, SSE offers:

  • Simpler implementation than WebSockets
  • Works through firewalls, proxies, and load balancers
  • Built-in reconnection and event resumption
  • Plain text debugging

Next time you need real-time updates, ask yourself: do I really need bidirectional communication? If not, SSE might be the simpler solution you need.

More to Read