Regio
Fastify · PostgreSQL · Redis · Expo / React Native · Caddy · MinIO · Docker
Regio gives you a region's silhouette and asks you to name it. Wrong guesses come back with distance, direction and adjacency hints, so you close in rather than shoot in the dark. There is a new set every day across 168 countries and 4,115 regions, plus streaks, a passport of stamps for places you have identified, leaderboards and a world tour mode. It runs in twelve languages and region names appear in the player's own.
The client is a single Expo/React Native codebase that ships as both the web app and the mobile app. Play is offline-tolerant by design: guesses live on the device first and are reconciled with the server afterwards, and rounds that never reached the server are queued and replayed rather than lost. Getting that reconciliation right - one score per puzzle, no matter which surface finished it - took more care than the guessing itself.
The backend is Fastify over PostgreSQL and Redis, and it is split along the three loads the product actually has, because they scale differently. Reads - the map and the leaderboards - are served from Redis and only need freshness measured in seconds. Writes are high-volume, small and abuse-prone, so they are rate-limited and buffered into Postgres. Money runs on its own path inside transactions with a ledger, because it is the one part with no tolerance for error.
Sponsorship is how it pays for itself: a sponsor's name appears on the result card every day their region is the answer. That means store receipts matter, and the rule there is to distrust the notification. Apple and Google webhooks are read only for which transaction changed; the actual entitlement is then fetched from the store under our own identity. A forged notification cannot grant anything, and a refunded user does not keep premium forever - the quietest failure on the money path.
Highlights
- One Expo codebase ships as both the web app and the mobile app
- Offline-tolerant play: guesses reconciled with the server, unsent rounds replayed rather than lost
- Reads from Redis, writes buffered into Postgres, money in its own transactional path with a ledger
- Store webhooks treated as untrusted: entitlement re-fetched from Apple and Google under our own identity
- Remote config for ads, maintenance and minimum version - changed without shipping an app update