Realtime Scoreboard
Build a live scoreboard service in Scala: scorekeepers send score changes for matches (such as a goal or a point) over HTTP, and every browser watching a match gets the update instantly over a WebSocket. The latest state of every match is saved, so after a restart the service comes back with the correct scores, and viewers who connect late or reconnect first receive the current score, then the live updates. It's built with http4s and Cats Effect, using fs2 Topics to broadcast updates. You'll learn WebSockets in http4s, broadcasting with fs2, managing shared state safely with Ref, persistence and recovery, validating commands, and testing streaming code.
- Estimate
- ~11.5h
- Steps
- 6
- Completed by
- 0
- Proposed by
- codeseed.app
Cats Effect · http4s
Project roadmap
- 01
Define the API
~1.5hWrite API.md: matches (id, teams, status scheduled, live or finished, and the score), commands sent by scorekeepers (start, add points for a team, correct a score and finish) with a version number to detect conflicting commands, the WebSocket at /matches/{id}/live that sends the current state then every update, and GET /matches. Done when: API.md defines every command, message and endpoint.
- 02
Hold state and apply commands
~2hModel matches and commands as case classes with Circe codecs. Keep all matches in a Ref[IO, Map[...]] and write a pure function that applies a command to a match, rejecting invalid ones (adding points to a finished match, negative scores, or a stale version). Done when: unit tests of the command logic pass for valid and invalid commands.
- 03
Broadcast over WebSockets
~2.5hCreate an fs2 Topic per match (or one Topic filtered by match). Add POST /matches/{id}/commands, which applies the command, stores the new state and publishes it, protected with a scorekeeper token. Add the WebSocket route with http4s's WebSocketBuilder: on connect send the current state, then stream the Topic's updates, handling client disconnects cleanly. Done when: several browser tabs see score changes instantly.
- 04
Save state and recover
~2hSave every accepted command and the latest match state to a database (PostgreSQL with Doobie, or SQLite), and load all matches at startup. Make repeated commands safe by rejecting duplicates by command id. Done when: restarting the service keeps every match's score, and viewers see the right score after reconnecting.
- 05
Handle slow and dropped clients
~1.5hLimit each subscriber's buffer so a slow client can't use unlimited memory (dropping old updates, since only the latest score matters), send pings to detect dead connections, and write a small browser client that reconnects with increasing waits and shows the connection state. Done when: a slow or disconnected client never affects others, and the browser client reconnects by itself.
- 06
Test it
~2hWrite munit-cats-effect tests: the command logic, the HTTP command endpoint (valid, invalid, stale and unauthorized), subscribers receiving the current state then updates, recovery from the database, and dropping updates for a slow subscriber. Done when: sbt test passes.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack