Case study 06
PitchPool
PitchPool runs prediction competitions. Participants join pools, predict match results and follow live leaderboards that update as each result is scored automatically. Every tenant gets its own isolated pools, participants and roles, and subscription plans decide which features a tenant can use.
- Founder & sole engineer
- Own product
- SaaS, Sports & Competition
- Multi-tenancy, Subscription Billing, Real-time Leaderboards, RBAC, Background Jobs
- Next.js
- React
- TypeScript
- NestJS
- PostgreSQL
- Prisma
- Redis
- BullMQ
- JWT/OAuth
- Docker
What PitchPool does
- Multi-tenant workspaces, each with its own pools, participants and roles
- Automated scoring of predictions as match results come in
- Live leaderboards, re-ranked after every scored result
- Platform, organisation and pool roles, with safeguards against privilege escalation
- Match results ingested automatically from an external feed
- Subscription plans with feature gating and webhook-driven billing
The problem
Prediction pools are quiet for days and then everyone arrives at once. When a match ends, thousands of predictions have to be scored and every leaderboard re-ranked while people refresh the page. In between, the platform should cost almost nothing to run.
Approach
The API stays thin and stateless. Match data arrives through a queue-backed ingestion worker that protects the upstream feed with rate limits and a circuit breaker. Scoring runs as idempotent jobs, so a retried job can never double-count. Scheduling lives in memory rather than in database polling, which lets serverless PostgreSQL sleep when nothing is happening.
Key decisions
- D1
Provider-agnostic billing
Plans and entitlements live in our own schema; the payment provider sits behind a gateway interface and reaches us only through verified webhooks.
- D2
Three-tier RBAC
Platform, organisation and pool roles are checked separately, with explicit guards against granting a role higher than your own.
- D3
Idle should be free
Background tasks were consolidated and moved off database polling so the database can scale to zero between match days.
Highlights
- Shipped subscription billing (plans, entitlement gating and webhooks) behind a provider-agnostic gateway; designed three-tier RBAC with privilege-escalation safeguards.
- Cut idle database traffic by ~90% through in-memory job scheduling and consolidated tasks, enabling serverless PostgreSQL to scale to zero.
- Built BullMQ ingestion with rate limiting, circuit breaking and retry/backoff, keeping leaderboards accurate through match-day spikes.
My contributions
- Came up with the product idea and defined its scope
- Designed the architecture: the multi-tenant data model, queue-based ingestion and scoring, and billing
- Built the whole stack alone: Next.js frontend, NestJS API, PostgreSQL with Prisma, and Redis/BullMQ workers
- Deployed it to production at pitchpool.io
Challenges
- Traffic is near zero for days, then spikes when a match ends and every prediction has to be scored at once
- Keeping leaderboards accurate when the upstream match feed is slow, rate-limited or failing
- Letting serverless PostgreSQL scale to zero between match days, which meant moving scheduled work off database polling