const { EventEmitter } = require('events');
const HIGH_WATER_MARK = 1 << 20; // 1 MiB of buffered bytes per client
class SseHub extends EventEmitter {
constructor() {
super();
this.clients = new Set();
this.lastId = 0;
}
addClient(res) {
this.clients.add(res);
this.emit('connect', this.clients.size);
return () => this.removeClient(res);
}
removeClient(res) {
if (this.clients.delete(res)) {
this.emit('disconnect', this.clients.size);
}
}
formatEvent({ id, event, data, retry }) {
let frame = '';
if (retry != null) frame += `retry: ${retry}\n`;
if (id != null) frame += `id: ${id}\n`;
if (event) frame += `event: ${event}\n`;
const payload = typeof data === 'string' ? data : JSON.stringify(data);
for (const line of payload.split('\n')) {
frame += `data: ${line}\n`;
}
return frame + '\n';
}
broadcast(event, data) {
const id = ++this.lastId;
const frame = this.formatEvent({ id, event, data });
for (const res of this.clients) {
if (res.writableEnded || res.writableLength > HIGH_WATER_MARK) {
this.removeClient(res);
res.destroy();
continue;
}
res.write(frame);
}
return id;
}
}
module.exports = new SseHub();
const hub = require('./SseHub');
const HEARTBEAT_MS = 15000;
const RETRY_MS = 3000;
function sseHandler(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
'X-Accel-Buffering': 'no'
});
res.flushHeaders();
// Advise the client how long to wait before reconnecting.
res.write(hub.formatEvent({ retry: RETRY_MS, event: 'ready', data: 'ok' }));
const resumeFrom = Number(req.headers['last-event-id']) || 0;
if (resumeFrom) {
res.write(hub.formatEvent({ event: 'resume', data: { from: resumeFrom } }));
}
const unregister = hub.addClient(res);
const heartbeat = setInterval(() => {
if (res.writableEnded) return clearInterval(heartbeat);
res.write(`: ping ${Date.now()}\n\n`);
}, HEARTBEAT_MS);
req.on('close', () => {
clearInterval(heartbeat);
unregister();
});
}
module.exports = sseHandler;
const http = require('http');
const hub = require('./SseHub');
const sseHandler = require('./sseHandler');
const PAGE = `<!doctype html><meta charset="utf-8"><title>SSE</title>
<pre id="log"></pre>
<script>
const es = new EventSource('/events');
const log = document.getElementById('log');
es.addEventListener('tick', (e) => {
log.textContent += 'tick #' + e.lastEventId + ' ' + e.data + '\\n';
});
es.onerror = () => log.textContent += '[reconnecting...]\\n';
</script>`;
const server = http.createServer((req, res) => {
if (req.url === '/events' && req.method === 'GET') {
return sseHandler(req, res);
}
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(PAGE);
});
// Producer decoupled from transport: fan the same event to every client.
setInterval(() => {
hub.broadcast('tick', { time: new Date().toISOString(), clients: hub.clients.size });
}, 1000);
hub.on('connect', (n) => console.log(`client connected (${n} total)`));
hub.on('disconnect', (n) => console.log(`client left (${n} remaining)`));
server.listen(3000, () => console.log('SSE server on http://localhost:3000'));
Server-Sent Events (SSE) are a one-way streaming protocol that lets a server push text frames to browsers over a single long-lived HTTP response. Unlike WebSockets, SSE rides on plain HTTP, reconnects automatically, and is trivial to implement with only Node's core http module — no framework required. This snippet shows the full loop: a broadcast hub, the connection handler that turns a request into a durable event stream, and a producer that feeds it.
In SseHub, connections are modeled as a set of live response objects. The hub extends EventEmitter mostly for convention; the real work is addClient, which registers a res, and broadcast, which serializes a payload into the SSE wire format via formatEvent. That format is strict: each field is a field: value line, multi-line data is split so every line gets its own data: prefix, and a blank line terminates the frame. An incrementing lastId is attached as the id: field so clients can resume with Last-Event-ID after a drop. The hub also tracks res.writableLength against a HIGH_WATER_MARK to shed slow consumers, which prevents one stalled client from ballooning memory — a real pitfall with fan-out streaming.
In sseHandler, the response is switched into streaming mode by writing SSE headers: Content-Type: text/event-stream, Cache-Control: no-cache, and Connection: keep-alive. Crucially res.flushHeaders() sends them immediately so the browser opens the stream before any data arrives. A retry: directive tells the client how long to wait before reconnecting. A heartbeat comment (: ping) is written on an interval to keep proxies and load balancers from idling the socket closed. The handler wires req.on('close') to unregister the client and clear timers, avoiding leaks when a tab closes.
In server.js, an http.createServer routes /events to the handler and everything else to a tiny page. A setInterval drives hub.broadcast, demonstrating a producer decoupled from transport. This design scales to thousands of idle connections cheaply, but note SSE is unidirectional and capped at ~6 connections per origin over HTTP/1.1, so HTTP/2 or a single multiplexed stream is preferable at scale.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
package api
import (
"io"
"net/http"
"os"
Safe multipart uploads using temp files (bounded memory)
package deps
import (
"crypto/tls"
"crypto/x509"
"net/http"
mTLS client configuration with custom root CA pool
Share this code
Here's the card — post it anywhere.