DEV Community

1xApi
1xApi

Posted on • Edited on • Originally published at 1xapi.com

5 Server-Sent Events Patterns Every API Developer Should Know in 2026

As of March 2026, Server-Sent Events (SSE) are experiencing a renaissance. While WebSockets got all the attention for years, SSE is making a comeback—simpler, works over standard HTTP/2, automatic reconnection, and perfect for server-to-client streaming.

Here are 5 patterns you need to know:

1. Basic SSE Endpoint

The simplest pattern—just stream events to the client:

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

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

  req.on('close', () => clearInterval(interval));
});
Enter fullscreen mode Exit fullscreen mode

Client-side:

const evtSource = new EventSource('/api/events/stream');
evtSource.onmessage = (e) => console.log(JSON.parse(e.data));
Enter fullscreen mode Exit fullscreen mode

2. Named Events for Message Routing

Send specific event types clients can listen for:

// Server
function sendNotification(res, type, payload) {
  res.write(`event: ${type}\n`);
  res.write(`data: ${JSON.stringify(payload)}\n\n`);
}

sendNotification(res, 'user_joined', { user: 'alice' });
sendNotification(res, 'price_update', { price: 42.50 });
Enter fullscreen mode Exit fullscreen mode
// Client
const source = new EventSource('/api/stream');
source.addEventListener('price_update', (e) => {
  const data = JSON.parse(e.data);
  updatePriceDisplay(data.price);
});
Enter fullscreen mode Exit fullscreen mode

3. Automatic Reconnection with Last-Event-ID

Never lose sync—track where you left off:

// Server: respect Last-Event-ID header
app.get('/api/logs/stream', (req, res) => {
  const lastId = req.headers['last-event-id'];
  let cursor = lastId ? parseInt(lastId) : 0;

  const stream = db.logs.find({ id: { $gt: cursor } }).cursor();

  stream.on('data', (log) => {
    res.write(`id: ${log.id}\n`);
    res.write(`data: ${JSON.stringify(log)}\n\n`);
  });
});
Enter fullscreen mode Exit fullscreen mode

The browser automatically reconnects and sends Last-Event-ID. Critical for production!

4. Heartbeat to Prevent Timeout

Keep connections alive over proxies and load balancers:

app.get('/api/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');

  const heartbeat = setInterval(() => {
    res.write(': heartbeat\n\n'); // comment = ignored by client
  }, 15000); // every 15 seconds

  req.on('close', () => clearInterval(heartbeat));
});
Enter fullscreen mode Exit fullscreen mode

The : prefix makes it a comment—client ignores it but the connection stays warm.

5. Graceful Degradation with Retry

Control reconnection behavior:

// Server: tell client to retry after 5 seconds if disconnected
res.write(`retry: 5000\n\n`);

// Or dynamically based on load
const retryMs = load > 80 ? 10000 : 2000;
res.write(`retry: ${retryMs}\n\n`);
Enter fullscreen mode Exit fullscreen mode

When to Use SSE vs WebSockets

Scenario Use SSE Use WebSockets
Server → Client only
Bidirectional
HTTP/2 multiplexing ⚠️
Binary data
Simple real-time updates
Firewall-restricted networks

Bottom line: For dashboards, notifications, live feeds—SSE wins. For games, collaborative editing, complex bi-directional sync—WebSockets.

Quick Tip: CORS for SSE

If your API and frontend are on different domains:

app.get('/api/stream', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://your-app.com');
  res.setHeader('Access-Control-Expose-Headers', 'Last-Event-ID');
  // ... rest of SSE setup
});
Enter fullscreen mode Exit fullscreen mode

That's it! SSE is underutilized in 2026—give your users a lighter real-time experience.

Top comments (0)