Skip to content

Sse Project

DodaTech 5 min read

title: "SSE Project: Live Data Dashboard" description: "Build a complete live data dashboard with Server-Sent Events including multi-source streaming, real-time charts, and event recovery." weight: 22 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "sse"]


This project brings together everything you have learned about SSE. You will build a complete live data dashboard with multiple event sources, real-time visualization, and reliable event recovery.

## Project Overview

Build a live operations dashboard that streams multiple data sources (server metrics, application logs, user activity) through SSE and displays them in real-time with charts, tables, and alerts.

## What You'll Build

- SSE server with multiple named event types
- Client with event-type-specific handlers
- Real-time charts updating via SSE
- Event recovery with Last-Event-ID
- Connection status monitoring
- Multiple data source integration

## Why This Project

This project simulates a real-world monitoring dashboard similar to Datadog, Grafana, or New Relic. It exercises all major SSE patterns in a realistic application.

## Flow Chart

```mermaid
flowchart TD
    A[SSE Server] --> B[Event: metrics]
    A --> C[Event: logs]
    A --> D[Event: activity]
    A --> E[Event: alerts]
    B --> F[Dashboard Client]
    C --> F
    D --> F
    E --> F
    F --> G[CPU/Memory Charts]
    F --> H[Log Viewer]
    F --> I[Activity Feed]
    F --> J[Alert Panel]

Architecture

Server Implementation

const http = require('http');
const { EventEmitter } = require('events');

class SSEDashboardServer {
  constructor() {
    this.emitter = new EventEmitter();
    this.eventBuffer = [];
    this.MAX_BUFFER = 5000;
  }

  handleConnection(req, res) {
    const lastEventId = parseInt(
      req.headers['last-event-id'] || '0'
    );

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

    // Send missed events
    const missedEvents = this.eventBuffer.filter(
      e => e.id > lastEventId
    );
    missedEvents.forEach(event => {
      res.write(
        `id: ${event.id}\n` +
        `event: ${event.type}\n` +
        `data: ${JSON.stringify(event.data)}\n\n`
      );
    });

    // Subscribe to new events
    const listener = (event) => {
      res.write(
        `id: ${event.id}\n` +
        `event: ${event.type}\n` +
        `data: ${JSON.stringify(event.data)}\n\n`
      );
    };

    this.emitter.on('event', listener);

    req.on('close', () => {
      this.emitter.off('event', listener);
    });
  }

  emit(type, data) {
    const event = {
      id: Date.now(),
      type,
      data: { ...data, _timestamp: Date.now() },
    };

    this.eventBuffer.push(event);
    if (this.eventBuffer.length > this.MAX_BUFFER) {
      this.eventBuffer.shift();
    }

    this.emitter.emit('event', event);
  }
}

const dashboard = new SSEDashboardServer();

// Start HTTP server
http.createServer((req, res) => {
  if (req.url === '/events') {
    dashboard.handleConnection(req, res);
  } else if (req.url === '/status') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      bufferedEvents: dashboard.eventBuffer.length,
    }));
  } else {
    res.writeHead(404);
    res.end();
  }
}).listen(3000);

// Simulate metrics
setInterval(() => {
  dashboard.emit('metrics', {
    cpu: Math.random() * 100,
    memory: Math.random() * 100,
    disk: Math.random() * 100,
    network: Math.random() * 1000,
  });
}, 2000);

// Simulate logs
setInterval(() => {
  const levels = ['info', 'warn', 'error'];
  dashboard.emit('log', {
    level: levels[Math.floor(Math.random() * 3)],
    service: ['api', 'worker', 'db'][Math.floor(Math.random() * 3)],
    message: `Sample log message at ${Date.now()}`,
  });
}, 5000);

// Simulate user activity
setInterval(() => {
  dashboard.emit('activity', {
    user: `user-${Math.floor(Math.random() * 100)}`,
    action: ['login', 'view', 'create', 'update', 'delete'][Math.floor(Math.random() * 5)],
    resource: ['project', 'task', 'comment'][Math.floor(Math.random() * 3)],
  });
}, 3000);

// Simulate alerts
setInterval(() => {
  if (Math.random() > 0.7) {
    dashboard.emit('alert', {
      severity: ['critical', 'warning', 'info'][Math.floor(Math.random() * 3)],
      message: `CPU threshold exceeded: ${Math.random() * 100}%`,
      source: 'monitoring-agent-1',
    });
  }
}, 10000);

Client Implementation

class DashboardSSEClient {
  constructor(url) {
    this.url = url;
    this.lastEventId = parseInt(localStorage.getItem('dash-last-id') || '0');
    this.handlers = new Map();
    this.connect();
  }

  connect() {
    this.source = new EventSource(this.url);

    this.source.onopen = () => {
      this.emit('connection', 'connected');
    };

    this.source.addEventListener('metrics', (e) => {
      this.trackId(e);
      this.emit('metrics', JSON.parse(e.data));
    });

    this.source.addEventListener('log', (e) => {
      this.trackId(e);
      this.emit('log', JSON.parse(e.data));
    });

    this.source.addEventListener('activity', (e) => {
      this.trackId(e);
      this.emit('activity', JSON.parse(e.data));
    });

    this.source.addEventListener('alert', (e) => {
      this.trackId(e);
      this.emit('alert', JSON.parse(e.data));
    });

    this.source.onerror = () => {
      if (this.source.readyState === EventSource.CLOSED) {
        this.emit('connection', 'failed');
      } else {
        this.emit('connection', 'reconnecting');
      }
    };
  }

  trackId(event) {
    if (event.lastEventId) {
      this.lastEventId = parseInt(event.lastEventId);
      localStorage.setItem('dash-last-id', String(this.lastEventId));
    }
  }

  on(event, callback) {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event).add(callback);
    return () => this.handlers.get(event)?.delete(callback);
  }

  emit(event, data) {
    this.handlers.get(event)?.forEach(cb => cb(data));
  }
}

// Dashboard UI
const dashboard = new DashboardSSEClient('http://localhost:3000/events');

// Metrics charts
const cpuData = [];
const memoryData = [];
const CHART_POINTS = 60;

dashboard.on('metrics', (data) => {
  cpuData.push(data.cpu);
  memoryData.push(data.memory);
  if (cpuData.length > CHART_POINTS) cpuData.shift();
  if (memoryData.length > CHART_POINTS) memoryData.shift();

  updateCPUGauge(data.cpu);
  updateMemoryGauge(data.memory);
  updateMetricsChart();
});

// Log viewer
dashboard.on('log', (log) => {
  const logEl = document.getElementById('log-stream');
  const entry = document.createElement('div');
  entry.className = `log-entry log-${log.level}`;
  entry.innerHTML = `
    <span class="log-time">${new Date().toLocaleTimeString()}</span>
    <span class="log-level">${log.level}</span>
    <span class="log-service">${log.service}</span>
    <span class="log-message">${escapeHtml(log.message)}</span>
  `;
  logEl.insertBefore(entry, logEl.firstChild);
  if (logEl.children.length > 100) {
    logEl.removeChild(logEl.lastChild);
  }
});

// Activity feed
dashboard.on('activity', (activity) => {
  const feed = document.getElementById('activity-feed');
  const item = document.createElement('div');
  item.className = 'activity-item';
  item.textContent = `${activity.user} ${activity.action} ${activity.resource}`;
  feed.insertBefore(item, feed.firstChild);
  if (feed.children.length > 50) {
    feed.removeChild(feed.lastChild);
  }
});

// Alert panel
dashboard.on('alert', (alert) => {
  const panel = document.getElementById('alert-panel');
  const alertEl = document.createElement('div');
  alertEl.className = `alert alert-${alert.severity}`;
  alertEl.textContent = `[${alert.severity}] ${alert.message}`;
  panel.insertBefore(alertEl, panel.firstChild);

  // Flash for critical alerts
  if (alert.severity === 'critical') {
    document.body.classList.add('alert-flash');
    setTimeout(() => document.body.classList.remove('alert-flash'), 1000);
  }

  if (panel.children.length > 20) {
    panel.removeChild(panel.lastChild);
  }
});

// Connection status
dashboard.on('connection', (status) => {
  const indicator = document.getElementById('connection-status');
  indicator.className = `status-${status}`;
  indicator.textContent = status;
});

Common Mistakes

Mistake Explanation
Not cleaning up DOM elements Event streams can generate thousands of DOM nodes; always limit and trim
Blocking the main thread with DOM updates Batch DOM updates for high-frequency events using requestAnimationFrame
Missing event ID tracking Without IDs, reconnection misses events; buffer and replay on the server
Overwhelming the client with data Throttle high-frequency events client-side; use chart sampling for dense data
Not handling connection loss gracefully Show connection status, disable actions, and re-enable when reconnected

FAQ

How do I handle high-frequency metrics (100ms intervals)?

Use requestAnimationFrame for DOM updates. Sample or aggregate data on the server before sending. Consider differential updates.

What is the best way to limit DOM growth?

Set maximum children limits (50-100 for logs, 20 for alerts). Use a ring buffer pattern to remove oldest entries.

How do I export dashboard data?

Add a download button that captures the event buffer from the server. Implement a REST endpoint for historical data.

Can I add authentication to the dashboard?

Yes, use JWT tokens passed as query parameters or cookies. Validate on the server before establishing the SSE connection.

How do I make the dashboard mobile-friendly?

Use responsive design, reduce chart complexity on mobile, and throttle updates on slower connections.

What monitoring should I add to the dashboard?

Track connection status, event throughput, rendering performance (FPS), and last event timestamp for freshness monitoring.

Mini Project

Complete the full operations dashboard. Extend it with historical data loading (REST endpoint for past data), user authentication, customizable metric thresholds with alerting, multi-workspace support (different SSE channels per workspace), and a dark mode UI.

What's Next

Learn about Webhooks for server-to-server communication

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro