SSE Backend Setup
What your own backend needs to implement to work with Flux's Server-Sent Events realtime adapter — an HTTP streaming route with automatic backfill, single-state changelog integration, and an optional HTTP POST reconciliation endpoint.
- An existing Flux app already wired up on the client side, with SSE chosen as its realtime adapter.
- A backend server you control, in any language, capable of holding an HTTP response open and writing to it incrementally (this page shows Node and Python side by side).
- A data source that can tell you when a row changes — DB triggers, change streams, or your own event bus.
- A single-state changelog collection so backfill and deletion reconciliation have authoritative data to query.
Everything on this page is a description of plain-text HTTP output — required headers, a single-state changelog query, and streaming lines of text per event. None of it requires Node, or even a dedicated SSE library. Node is shown as the complete worked example because it's what most Flux SSE setups use, but every step includes a Python (FastAPI) equivalent, and the same wire format is just as producible from Go, Ruby, Java, or anything else that can hold an HTTP response open and write to it. If your backend already runs in one of those, write the same three headers and the same "event: / data: / blank line" text — nothing about it assumes a JavaScript runtime on the other end.
The contract you're building against
EventSource per channel and expects that connection to stay open indefinitely, pushing events as they happen. Every event you send down it, regardless of whether it's a live update or a catch-up backfill frame, needs to match one normalized shape:Install your server dependencies
npm install express cors dotenv mongoose. Python: pip install fastapi "uvicorn[standard]" motor — no SSE-specific package is required in either case; StreamingResponse is built into FastAPI, and Express handles HTTP response streaming natively.A per-channel client registry
asyncio.Queue per client since StreamingResponse pulls from an async generator.The SSE route: registration, backfill, and heartbeats
queryChangesSince(channel, syncAnchor). Because buildChannelUrl on the client always appends ?syncAnchor= (defaulting to 0), backfill runs unconditionally on every connection attempt.Always add the response stream to registerSseClient before executing the backfill database query. If you query first and register second, a mutation occurring while the query is in flight will neither be in the backfill dataset nor forwarded to the live client registry, resulting in a dropped event.
The changelog: single-state delta storage
op: 'DELETE' event, which a standard query on a live database table cannot accomplish.Query helpers: backfill and deletion check
queryChangesSince) and the POST-based reconciliation endpoint (queryExistingIds).Reconciliation endpoint (HTTP POST)
reconcileUrl parameter and issues an HTTP POST request containing locally cached IDs.Wiring your data source to broadcast and log
logChange() so backfill and reconciliation have state, and call broadcastToSse() to stream live frames to connected clients.Mounting the server
Client store adapters: consuming the wire format
setState handles both full array payloads (from initial bootstrap) and single delta objects (from SSE live frames and backfill).Testing your endpoint
curl with the unbuffered flag (-N):: connected, followed by backfill event frames. Trigger a mutation in your database to see instant event: UPDATE blocks print to stdout.Reverse proxy buffering — behind Nginx, set proxy_buffering off; for the stream location block alongside the X-Accel-Buffering: no header; otherwise, proxies hold frames until the connection closes.
Per-domain connection limits — browsers enforce a hard limit of ~6 concurrent HTTP/1.1 connections per origin. Serve your API over HTTP/2 to allow unlimited multiplexed SSE streams on a single connection.
Auth on EventSource — standard browser EventSource cannot set custom HTTP headers. Pass short-lived auth tokens as URL query parameters or rely on HttpOnly same-origin cookies, verifying permissions prior to client registration.
Worker process count — because SSE connections are persistent, broadcasts must span multiple server processes. Use Redis pub/sub to bridge instances when scaling horizontally across cluster workers or container nodes.
On the client side, this is the server the SSE tab in getting-started/react and getting-started/nextjs connects to — state management remains fully decoupled on the client. For deeper details on sync anchors and delta catch-up strategies across transports, see core-concepts/sync-anchors.