Skip to content

Sse Express

DodaTech 5 min read

title: "SSE with Express.js" description: "Learn how to implement Server-Sent Events in Express.js applications for real-time data streaming with route-specific event handlers." weight: 15 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "sse"]


Express.js makes it easy to add SSE endpoints alongside REST APIs. Using standard HTTP response objects, you can stream events to clients without additional dependencies.

## What You'll Learn

- Setting up SSE routes in Express
- Managing connected clients
- Broadcasting events to multiple clients
- SSE with authentication middleware
- Heartbeat and keepalive

## Why It Matters

Express is the most popular Node.js web framework. Adding SSE to existing Express applications enables real-time features without changing your architecture.

## Real-World Use

A SaaS application adds SSE endpoints to its Express API for real-time notifications. Users receive billing alerts, team activity updates, and system notifications without any additional infrastructure.

## Flow Chart

```mermaid
flowchart LR
    A[Express Server] --> B[REST API Routes]
    A --> C[SSE Route: /events]
    C --> D{Client Manager}
    D --> E[Client 1]
    D --> F[Client 2]
    D --> G[Client N]
    H[Application Events] --> D

Code Examples

Example 1: Basic SSE Route in Express

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

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*',
  });

  // Send initial connected event
  res.write(`event: connected\ndata: {"status": "connected"}\n\n`);

  // Send periodic updates
  const intervalId = setInterval(() => {
    const data = JSON.stringify({
      time: new Date().toISOString(),
      random: Math.random(),
    });
    res.write(`data: ${data}\n\n`);
  }, 5000);

  // Cleanup on disconnect
  req.on('close', () => {
    clearInterval(intervalId);
    console.log('Client disconnected');
  });
});

app.listen(3000);

Expected output: Express route serves SSE at /events, sending updates every 5 seconds and cleaning up on disconnect.

Example 2: Client Manager for Broadcasting

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

class SSEClientManager {
  constructor() {
    this.clients = new Map();
    this.clientIdCounter = 0;
  }

  addClient(res) {
    const id = ++this.clientIdCounter;
    this.clients.set(id, res);
    
    res.on('close', () => {
      this.clients.delete(id);
      console.log(`Client ${id} disconnected. Total: ${this.clients.size}`);
    });

    console.log(`Client ${id} connected. Total: ${this.clients.size}`);
    return id;
  }

  sendToAll(data, eventType = null) {
    const message = eventType
      ? `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`
      : `data: ${JSON.stringify(data)}\n\n`;

    this.clients.forEach((client, id) => {
      try {
        client.write(message);
      } catch (error) {
        console.error(`Error sending to client ${id}:`, error);
        this.clients.delete(id);
      }
    });
  }

  broadcast(event, data) {
    this.sendToAll(data, event);
  }

  getClientCount() {
    return this.clients.size;
  }
}

const sseManager = new SSEClientManager();

// SSE endpoint
app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  const clientId = sseManager.addClient(res);

  // Filter by event type
  const filters = req.query.filters?.split(',') || null;
  res._sseFilters = filters;
});

// API endpoint that broadcasts events
app.post('/api/broadcast', express.json(), (req, res) => {
  const { event, data } = req.body;
  
  sseManager.broadcast(event || 'message', data);
  res.json({ sent: sseManager.getClientCount() });
});

// Periodic heartbeat
setInterval(() => {
  sseManager.sendToAll({ type: 'heartbeat', time: Date.now() }, 'heartbeat');
}, 30000);

// Status endpoint
app.get('/api/sse-status', (req, res) => {
  res.json({
    connectedClients: sseManager.getClientCount(),
  });
});

app.listen(3000, () => {
  console.log('Express SSE server on port 3000');
});

Expected output: Client manager tracks all SSE connections and provides broadcast capabilities to server-side code.

Example 3: Authenticated SSE with Express

const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser());

// Authentication middleware
const authenticateSSE = (req, res, next) => {
  const token = req.query.token || req.cookies?.token;

  if (!token) {
    res.status(401).json({ error: 'Authentication required' });
    return;
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(403).json({ error: 'Invalid token' });
  }
};

// Per-user SSE endpoint
app.get('/events/user', authenticateSSE, (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  const userId = req.user.id;

  // Send user-specific events
  res.write(`data: ${JSON.stringify({
    type: 'connected',
    userId,
    message: 'Personal event stream started',
  })}\n\n`);

  // Subscribe to user-specific channels
  const userChannel = subscribeToUserChannel(userId, (event) => {
    res.write(`event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`);
  });

  req.on('close', () => {
    userChannel.unsubscribe();
  });
});

// Role-based SSE
app.get('/events/admin', authenticateSSE, (req, res) => {
  if (req.user.role !== 'admin') {
    res.status(403).json({ error: 'Admin access required' });
    return;
  }

  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
  });

  // Admin-only events
  const adminChannel = subscribeToAdminChannel((event) => {
    res.write(`data: ${JSON.stringify(event)}\n\n`);
  });

  req.on('close', () => adminChannel.unsubscribe());
});

// Event helpers
function subscribeToUserChannel(userId, callback) {
  // Subscribe to message queue for user
  const interval = setInterval(() => {
    callback({
      type: 'notification',
      data: { userId, message: 'Check your notifications' },
    });
  }, 10000);

  return { unsubscribe: () => clearInterval(interval) };
}

function subscribeToAdminChannel(callback) {
  const interval = setInterval(() => {
    callback({
      type: 'system-health',
      cpu: Math.random() * 100,
      memory: Math.random() * 100,
    });
  }, 5000);

  return { unsubscribe: () => clearInterval(interval) };
}

app.listen(3000);

Expected output: Authenticated SSE endpoints that deliver user-specific and role-based events.

Common Mistakes

Mistake Explanation
Not setting keep-alive headers Without explicit Connection: keep-alive, proxies may close the connection
Forgetting to handle req.on('close') Resources leak if cleanup is not performed on disconnection
Blocking the event loop SSE handlers should not perform CPU-intensive operations synchronously
Not filtering by user Broadcasting all events to all clients wastes bandwidth; filter server-side
Missing CORS headers Browser SSE clients need appropriate CORS headers for cross-origin access

Practice Questions

  1. How do you add an SSE endpoint to an Express application?
  2. How do you broadcast events to all connected SSE clients?
  3. How do you authenticate SSE connections in Express?
  4. How do you implement per-user event filtering?
  5. How do you handle client disconnections properly?

Challenge

Build an Express application with both REST and SSE endpoints. The REST API allows any authenticated user to trigger events, and the SSE endpoint broadcasts those events to connected clients with proper authorization.

FAQ

Can I use Express middleware with SSE routes?

Yes, SSE routes are regular Express routes. All middleware (auth, logging, CORS) works normally.

How do I handle SSE when using Express Router?

Create SSE routes on the Router just like regular routes. The same client manager pattern works.

Does SSE work with Express compression?

No, do not use compression middleware (like compression) for SSE endpoints because it buffers the stream.

How do I debug SSE in Express?

Use curl: curl -N http://localhost:3000/events. The -N flag disables buffering to see events in real-time.

Can I send SSE events from non-request contexts?

Yes, use a client manager singleton that other parts of your application can access to broadcast events.

How do I handle SSE behind a reverse proxy?

Configure NGINX or other proxies to disable buffering for SSE paths. Use proxy_buffering off and proxy_cache off.

Mini Project

Build a real-time activity feed for a team collaboration app using Express SSE. Include per-user event streams, broadcasting for team-wide announcements, and integration with existing authentication middleware.

What's Next

Learn how to implement SSE with Python

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro