Skip to main content
We describe the architecture using the C4 model: four levels of zoom, from “who uses it” down to “what the code does.” Each level answers a different question for a different reader.

Level 1: System Context

Who and what interacts with Sportz? There is no authentication layer: the “operator” is anyone with API access. That is a deliberate scope decision for a demo platform; see Project Status.

Level 2: Containers

What are the deployable units, and how do they talk? The single most important structural fact: REST and WebSocket share one HTTP server. Express handles normal requests; an upgrade listener intercepts /ws connections before they reach Express and hands them to the ws server. One process, one port, one deployable container.

Level 3: Components (Backend)

Inside the sportz container, what are the pieces? The broadcast functions are created inside attachWebSocketServer() and injected onto app.locals, so the REST routes can trigger a WebSocket broadcast without importing the WS module directly. This is the seam that connects “data was written” to “tell the clients.”

Level 4: Code

The seam, in actual code. When a commentary event is posted:

Scalability: what changes as load grows

The architecture above is correct for its current scale. Here is what breaks first, and when: Note the nuance: Sportz already has a pub/sub pattern (subscribe to match rooms → broadcast to subscribers). What’s missing is making that pub/sub distributed across instances. The change at 100k+ isn’t “add pub/sub”; it’s “move the pub/sub you already have from in-process memory to a shared backbone.” The key insight for an interview or a design review: the in-process broadcast is not a flaw: it’s the right choice until you horizontally scale the API. Adding a Redis backbone before then is premature complexity.