Everyone on StudyTomato is on the same clock. The timer isn’t stored per user: every client derives the phase from UTC (25 minutes of study, 5 of Minesweeper, a 30-minute cycle), so there’s no state to sync and a fresh page load is always correct. The server runs the shared Minesweeper game and pushes it over WebSockets. Visitors can play as a guest without signing up, and an admin can override the cycle live. A mine costs points and ten seconds instead of the rest of your break, and a cleared board is replaced by a new one, so all five minutes stay playable.
- Back end: FastAPI, JWT auth, async WebSocket connection manager.
- Front end: React and TypeScript, with API types generated from the back end’s OpenAPI schema.
- Hosting: started on AWS (EC2 for the API, S3 + CloudFront for the front end), then moved to Docker Compose on my homelab to stop paying for cloud hosting. The front end is now served from this site, and the API still runs on the homelab.
A decision I made on purpose: I considered Redis to share timer state across FastAPI workers, but a single worker is enough at this traffic, and the UTC-derived timer removes most of the shared state anyway. Redis is the next step if it ever needs more than one worker.