Case study · Product · 2023 – present
TennisBooker
Books tennis and pickleball courts the moment booking opens, every morning.
- Role
- Solo, full-stack (2023 version with help from Douglas Chen)
- When
- 2023 – present
- With
- Douglas Chen
- Repo
- JavaScript · ★ 1 · last push May 2025
01 The problem
At our local club, courts open for booking at 7:30 a.m. a week ahead and go fast. My parents set an alarm every week to get theirs.
02 What I built
I automated it in phases, starting in 2023 with a version I called Alfred. Phase 1 was a Playwright script on a GitLab schedule that booked from a hard-coded JSON file. Phase 2 added a React UI so my parents could manage their own schedule; then a database, Twilio texts with the results, and Auth0 logins with machine-to-machine tokens for the bot. My brother Douglas joined over a winter break and worked on the court configuration.
In 2026 I rewrote it from scratch as one TypeScript monorepo that members use to set weekly schedules, with the bot booking for them each morning.
03 Highlights
- The bot logs in at 7:25, builds every reservation, then fires at exactly 7:30 a.m. ET for courts 7 days out, falls back to backup courts and texts results via Twilio.
- TypeScript monorepo (Turborepo): Express + Prisma/PostgreSQL API with Auth0 roles and machine-to-machine tokens for the bot, and a React, MUI and TanStack Query web app.
- Runs in Docker on a Raspberry Pi: CI builds multi-arch images to GHCR, deploys go staging → smoke test → promote with one-command rollback, and Vitest and Playwright suites cover the code.
- Began in 2023 as “Alfred”: a Playwright bot on a cron-scheduled GitLab pipeline, with a React and Express app, Firebase and multi-user Auth0 login, used by 2 clients. In Aug 2026 the bot moved to the booking platform’s new GraphQL API.
04 Key decisions
Prepare early, fire on the second
Courts go in moments, so the slow work (logging in, building each reservation) happens five minutes early. At 7:30 the bot only submits, and falls back to backup courts.
Ship in phases
Each phase solved the previous one’s biggest pain (a hard-coded schedule, then manual checking, then security) instead of building the finished product up front.
Separate identities for people and the bot
Members sign in through Auth0 with roles; the bot uses machine-to-machine tokens to call the same API.
Deploys that can be undone
It runs on a Raspberry Pi at home, so every release goes through staging and a smoke test first, and a bad one is a single rollback command away.
05 Architecture
- 01Web appReact, MUI, TanStack Query
- 02APIExpress, Prisma, PostgreSQL, Auth0
- 03BotLogin 7:25, fire 7:30 ET
- 04Booking platformGraphQL API (since Aug 2026)
- 05TextsResults via Twilio
The 2026 version. The 2023 version (the public TennisUI repo) had the same shape with Firebase behind an Express API.
06 Stack
- TypeScript
- Playwright
- The original bot drove the booking site; it now also runs the end-to-end tests.
- React
- Express
- PostgreSQL
- Auth0
- Member logins with roles, and M2M tokens for the bot.
- Docker
- Multi-arch images so the same build runs in CI and on the Pi.
07 Links & sources
The project’s links and the public sources this write-up draws on.
08 Also on GitHub
- v2
- fullstack-portfolioA full stack portfolio made using React, Sass, and Sanity
- movie_websitePersonal movies from Firebase database to full-stack web application
- mathcheckTries to find all open assignments in Childsmath for course 2Z03 and send a SMS to your number. Uses Selenium and Python.
- Automate-runtimes-genres-from-movie-titlesRead google sheets database of movie titles and write runtimes and genres, from IMDB, into the database