Blog

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
On this page
  1. Why SSE?
  2. When to Use SSE vs WebSockets
  3. How SSE Works
  4. Server Implementation (Node.js/Express)
  5. Client Implementation
  6. Key Features
  7. Automatic Reconnection
  8. Event IDs for Resumption
  9. Event Types
  10. Production Considerations
  11. Scaling
  12. Security
  13. Monitoring
  14. Conclusion

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:

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:

javascript
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:

javascript
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

javascript
// 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

javascript
// 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.

Keep reading