typescript 111 lines · 3 tabs

SSE endpoint for server-to-browser events

Shared by codesnips Jan 2026
3 tabs
import { Response } from 'express';

type Client = { id: number; res: Response };

const clients = new Map<string, Set<Client>>();
let nextId = 1;

export function subscribe(userId: string, res: Response): () => void {
  const client: Client = { id: nextId++, res };
  const set = clients.get(userId) ?? new Set<Client>();
  set.add(client);
  clients.set(userId, set);

  return () => {
    const current = clients.get(userId);
    if (!current) return;
    current.delete(client);
    if (current.size === 0) clients.delete(userId);
  };
}

function writeEvent(res: Response, event: string, data: unknown, id: number): void {
  res.write(`id: ${id}\n`);
  res.write(`event: ${event}\n`);
  res.write(`data: ${JSON.stringify(data)}\n\n`);
}

export function publish(userId: string, event: string, data: unknown): void {
  const set = clients.get(userId);
  if (!set) return;
  for (const client of set) {
    writeEvent(client.res, event, data, client.id);
  }
}
3 files · typescript Explain with highlit

Server-Sent Events (SSE) is a one-way streaming protocol built on top of a long-lived HTTP response: the server keeps the connection open and pushes text/event-stream frames, while the browser's native EventSource handles parsing and automatic reconnection. It is a good fit for push notifications, live counters, or activity feeds where traffic flows server-to-client only and the operational cost of WebSockets is not justified.

In sseHub.ts the process keeps an in-memory registry of open connections in a Map keyed by user id. subscribe records the Response object and returns an unsubscribe function so callers can clean up on disconnect. publish serializes a payload into the SSE wire format inside writeEvent — a event: line, an incrementing id: line used for resumption, and a data: line — then flushes it to every response registered for that user. Because SSE frames are terminated by a blank line, the trailing \n\n is essential; omitting it means the browser buffers the event indefinitely.

notifications.ts wires the hub into an Express route. It sets the streaming headers (Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive) and calls flushHeaders() so the client receives the response head immediately rather than waiting for the first body chunk. A setInterval heartbeat writes a comment line (:\n\n) every 15 seconds to defeat idle proxies and load balancers that would otherwise kill a quiet connection. The req.on('close') handler clears the timer and unsubscribes, which is the single most common source of leaks in naive SSE code.

useServerEvents.ts is the client half. It opens an EventSource, attaches a named listener for the notification event, and exposes the latest parsed message plus a connection status. EventSource reconnects on its own, so the hook only needs to reflect state and close the socket on unmount. A caveat worth noting: browsers cap concurrent EventSource connections per domain over HTTP/1.1, and this in-memory hub does not survive multiple server instances, so a production deployment would back publish with Redis pub/sub.


Related snips

ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs
typescript
import React from "react";

type FallbackProps = {
  error: Error;
  reset: () => void;
};

React Error Boundary + error reporting hook

react frontend error-boundary
by codesnips 3 tabs
javascript
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
  openAnalyzer: true,
});

/** @type {import('next').NextConfig} */

Next.js bundle analyzer for targeted performance work

nextjs performance tooling
by codesnips 4 tabs

Share this code

Here's the card — post it anywhere.

SSE endpoint for server-to-browser events — share card
Link copied