javascript 119 lines · 3 tabs

Broadcasting Events to Clients with Server-Sent Events on Node's http Module

Shared by codesnips Aug 2026
3 tabs
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();
3 files · javascript Explain with highlit

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

Share this code

Here's the card — post it anywhere.

Broadcasting Events to Clients with Server-Sent Events on Node's http Module — share card
Link copied