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