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

  1. 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.

  2. 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.

  3. 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.

  4. 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

  1. 01Web appReact, MUI, TanStack Query
  2. 02APIExpress, Prisma, PostgreSQL, Auth0
  3. 03BotLogin 7:25, fire 7:30 ET
  4. 04Booking platformGraphQL API (since Aug 2026)
  5. 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

All repositories on GitHub →