Good Fights
A fight-rating app for combat-sports fans — live on the App Store and Google Play, with a catalogue going back to 1993.
- Role
- Founder · sole developer
- Platforms
- iOS · Android · Web
- Stack
- React Native (Expo), Node/Express in TypeScript, PostgreSQL + Prisma, Next.js, Docker on a Linux VPS, Cloudflare R2
- Links
- goodfights.app · App Store · Google Play



The problem
Film fans have Rotten Tomatoes. Fight fans had nothing — no place to rate an individual fight, see what the crowd thought, or find the great ones they missed.
Fighting Tomatoes, my earlier web version, proved people wanted this. Good Fights is the mobile-first successor, built to live on both app stores with a real back-end behind it.
What shipped
- iOS and Android apps from one React Native codebase, published through App Store and Play Store review.
- Accounts with email and Google sign-in, and a migration that carried the existing Fighting Tomatoes user base onto the new platform — 3,415 registered users who have submitted 76,516 fight ratings and 1,450 written reviews.
- A catalogue of 15,730 fights across 1,635 events dating to 1993 — 10,567 fighters, results and stats — kept current by daily scrapers.
- A REST API in TypeScript on PostgreSQL, shared by the apps and the Next.js web version at goodfights.app.
- Claude-based enrichment that writes fight and fighter detail, gated on a confidence score so nothing the model is unsure about is published — with unit tests that check its output, including one that verifies quoted material is real.
- 34 scheduled GitHub Actions workflows running the scrapers, enrichment, database backups, deduplication and content-freshness checks.
- A Remotion video pipeline that renders promo clips from live database data, with generated voice-over, plus 3,670 automatically written news articles.
- Over-the-air updates so fixes reach users without a new store submission.
How it fits together
The hard parts
Two app stores, one release
Getting through Apple and Google review on every version, and keeping a release cadence that doesn't leave users stranded on an old build.
Updating without resubmitting
Over-the-air updates have rules about which builds they can target. Getting that right meant fewer store submissions and faster fixes.
Trusting a model in production
An LLM that is right most of the time is not good enough when the output is published as fact. Every enriched field carries a confidence score and is dropped below a threshold, and the enrichment code is unit-tested like any other service — including a test that checks quoted material against the source.
Running the whole stack myself
Docker and nginx on a Linux VPS, Prisma migrations against Postgres, Cloudflare R2 for images, cron for the overnight jobs, PostHog for behaviour. No platform team — scheduled jobs moved off paid CI onto a server I run, with enough monitoring to know when something breaks.
Outcome
- 76,516
- fight ratings submitted by users
- 3,415
- registered users
- 15,730
- fights catalogued across 1,635 events