SlateCourt - Badminton Venues & Games
A mobile-first PWA for Sydney badminton — a 54-venue directory with live court availability aggregated from the venues' own public booking pages, a game ledger with bill-splitting, and a friends-and-groups social layer. A personal, non-commercial demo, soft-launched invite-only with my own playing circle.
- Role
- Solo build · product → infra
- Year
- 2026
- Type
- PWA · Personal demo
- Status
- Live

Find a court. Remember the good ones. Settle up after.
Most venues have no booking API, so Slate never pretends to book for you — it shows what is genuinely open, sends you to the venue to book, then turns the game itself into the product: who came, what it cost, who has paid, what gear was used. Bilingual (en/zh), installable, and gated behind personal invite codes.
Why This Exists
A real product for a real circleBuilt for my own Sydney badminton circle: sign-up is invite-gated, every member mints a personal invite code, and the social layer — friends, groups, game-invite links — grew out of how we actually organise play each week.
No fake "live" promises. Court availability shows only where a venue's own booking system can actually be read — 16 of 54 venues today — and the rest stay an honest directory with one-tap redirect-out booking.
Personal Use Only
SlateCourt is a personal, non-commercial demo built for my own playing circle — registration requires an invite code. The availability feature reads only the public, log-in-free booking pages the venues themselves publish, at a low, cached cadence, and every result links back to the venue's own booking site. No venue data is republished or sold, and the project is not affiliated with any venue or booking provider.
At a Glance
By the numbersAcross the Stack
Seven dimensionsFrontend
PWA & interfaceBackend
Typed APIData
Persistence & cacheAuth
Invite-gatedAvailability
Scrape pipelineTesting
Quality gatesDevOps
07 · The layer that ships everythingSee It Live
Desktop · Mobile

How Live Availability Works
Scrape pipelineMost venues publish no API, but their public booking pages render date-parameterised HTML that any visitor's browser can open. A small Cloudflare Worker reads those log-in-free pages at a low, cached cadence, per-platform adapters parse them into normalised slot grids, and an in-process scheduler refreshes busy venues a little more often than quiet ones. Grids land in Redis and FastAPI serves them to the Discover filter and venue pages — every result links back to the venue's own booking site. Adding a venue is configuration, not code.
Venue config
(platform, base_url, id) — config, not code
Worker fetch
Public, log-in-free pages · cached
Adapters
YEPbooking · SportLogic HTML parsing
Normalise
Slot grids per court, per day
Schedule
APScheduler · hot/warm tiers
Cache
Redis grids with TTL
Serve
FastAPI availability endpoints
Discover
Live badge + open-at-your-times filter
Trade-offs
Decisions that heldA game ledger, not a booking engine
Most venues have no public booking API, so Slate never fakes in-app booking. You book through the venue; Slate records the game — who came, what it cost, who has paid. The record and the bill split are the retention loop.
Next.js + FastAPI split monorepo
SQLAlchemy 2.0 async exposes every Postgres feature the data model wants; FastAPI auto-emits OpenAPI, and openapi-typescript keeps the frontend contract typed end to end with zero hand-written API types.
Better-Auth up front, JWT verified behind
Best-in-class passkey UX for free in Next.js; FastAPI trusts a shared-secret JWT through one small dependency. Same-domain routing via Traefik means cookies just work — no CORS, no domain juggling.
Reading public pages via a Cloudflare Worker
The venues' booking pages are anonymous, date-parameterised HTML — no log-ins bypassed, no headless browser. A lightweight Worker reads them at a low, cached cadence for personal use, keeping the 8 GB origin box light.
Want the full walkthrough?
Happy to talk through the architecture, the decisions, and the trade-offs behind this build.


