Real-time broadcast architecture for a K-12 scheduling platform
Laravel, architecture, React, real-time, broadcast, websockets, PostgreSQL, Reverb
Real-time broadcast architecture for a K-12 scheduling platform

Real-time broadcast architecture for a K-12 scheduling platform

**One of the top contributors to the platform, I built the real-time broadcast layer that keeps 17 board-mutating scheduler actions live for every connected role without a single poll.** ## Outcome One of the top contributors to the platform, I built the real-time broadcast layer that keeps 17 board-mutating scheduler actions live for every connected role without a single poll. ## Context This was contract work on a K–12 school dispatch and scheduling platform built with Laravel 13, PostgreSQL, React 19, Reverb, and Horizon. I built the broadcast layer alongside the client team, whose product workflows serve multiple staff roles on the same live boards. ## Problem The platform is a K-12 school dispatch and scheduling platform built on Laravel 13, PostgreSQL, and React 19. When one staff member moves a student, approves a hall pass, or claims a queue item, other staff looking at the same board need to see it immediately: a stale board causes double-dispatch and missed hand-offs in a live school hallway. There are 17 board-mutating scheduler actions, and any of them can change what another role should be seeing right now. The core question was how to push those changes to the right screens, and only the right screens, in real time. ## Constraints - Multiple roles (teachers, counselors, nurses, admins, queue staff) each see a different slice of the same data, so a change is never a broadcast to everyone. - The stack is modern and opinionated: Laravel 13 with Reverb and Horizon on the back end, React 19 with TypeScript, TanStack Query, and Zustand on the front. - School data is sensitive, so channel authorization and payload minimization are correctness requirements, not niceties. - Product internals stay high-level here; the contribution level is shareable, the rest is relative. ## Decisions and trade-offs **WebSockets over polling.** Polling every board at the frequency this needs would hammer PostgreSQL and still lag. Rejected. The Laravel + React real-time broadcast path runs over a self-hosted Reverb WebSocket server, so the server pushes only when state actually changes. **Per-action events over a firehose.** The easy design is one "something changed, refetch" signal. Rejected: it wakes every client on every change and leaks nothing useful about what to update. Instead each action emits its own named event onto private, role-scoped channels (per-user and per-role), and each event computes exactly which channels its recipients listen on. Events carry ids and enums, not full records, so a channel never overshares. Broadcasts dispatch only after the database commits and run on a dedicated queue lane with bounded retries, so a client never sees a change that later rolled back. **Server-confirmed UI over optimistic writes.** Optimistic updates feel instant but invite divergence when a mutation is rejected by a scheduling rule, and divergence in a dispatch board is a safety problem. Rejected for the mutating path. The React layer treats the broadcast as the source of truth: an action fires, and the authoritative board update arrives over the socket and invalidates the relevant TanStack Query cache. Correctness wins over the last few milliseconds of perceived latency. **What breaks first at 10x load.** Not PostgreSQL, and not the React clients. The single point of failure is the broadcast fan-out itself: the queue lane feeding Reverb and the per-event channel computation, which runs lookups to resolve recipients for every event. At 10x concurrent actions that path saturates before the datastore does, so the headroom plan is horizontal Reverb capacity plus precomputed recipient routing, not a bigger database. ## Implementation I added named, post-commit Laravel events for each mutating action, delivered through dedicated queue capacity to private role- and user-scoped Reverb channels. The client team's React application consumes the minimal event payloads and invalidates the relevant TanStack Query cache only after the server confirms the change. ## Results The board stays live across every role with no polling, and the design has a named, honest scale limit rather than a hidden one. In a sustained push through 2026 I became one of the top contributors to the platform, and the broadcast architecture is the piece that makes a multi-role real-time scheduling platform feel like one shared surface. ## Takeaways Real-time systems need explicit recipient boundaries and server-confirmed state, not a broad “something changed” signal. Naming the fan-out bottleneck upfront turns the scale plan into an engineering decision rather than an incident response.