Simple to operate today. Ready to scale tomorrow.
Our prototype is built on a deliberately boring, proven stack: a modular monolith, PostgreSQL + PostGIS, Redis and background workers. That gives us speed to launch and a clean path to scale.
How the pieces fit together.
Clients call a stateless API. Heavy geospatial work and deliveries go through Redis queues to a separate worker process. Everything persists in PostgreSQL with PostGIS.
Client layer
One Expo / React Native codebase ships to iOS and Android. Tokens live in SecureStore, and Expo API routes proxy sensitive calls so secrets never reach the device.
API layer
A Bun + Express modular monolith. Each domain (auth, users, routes, matching, rides, payments, notifications) exposes only its index.ts, which keeps the boundaries ready for a service split.
Async layer
BullMQ on Redis runs matching, notification and request-expiry queues in a separate worker process that scales independently of the API.
Data layer
PostgreSQL with PostGIS for geometry and GIST indexes, managed through Drizzle ORM migrations. Redis handles OTPs, refresh tokens, rate limits and idempotency.
Twelve tables that model the entire commute economy.
Routes are both offers and requests, distinguished by is_driver. Matches link two routes with a score. Rides and payments follow from an accepted match.
Strict state machines. No ambiguous rides, no lost rupees.
Every ride follows an enforced state machine with a timestamp for each transition. Payments settle through Razorpay with signature verification, and the pilot/platform split is computed in integer paise.
Built like a fintech, because it moves money.
- Phone OTP login with short-lived JWT access tokens and rotating refresh tokens
- Role-based authorisation for rider, pilot and both
- Idempotency-Key on every mutating request, so mobile retries never double-book or double-charge
- Rate limiting and request IDs on every call for abuse protection and traceability
- Zod validation at the edge of every endpoint
- Razorpay signature verification plus a raw-body signed webhook
- All money stored as integer paise, with no floating-point errors
- KYC images in S3 / R2. The database stores only URLs and verification metadata
Modern, cost-efficient, and hireable.
Each bottleneck already has a planned next step.
The primary scaling metric is matching queue depth. Here is how each layer evolves as we grow city by city.
| Layer | In the prototype | At city scale |
|---|---|---|
| API | Stateless Bun instances behind a load balancer | Auto-scaled replicas + PgBouncer connection pooling |
| Matching | Single worker process, ≤ 100 candidates per job | N worker replicas on the same queue, H3 geo-bucketing before PostGIS |
| Geometry | Endpoint radius + LineString corridor (ST_DWithin) | Shared-length overlap via ST_Intersects on route lines |
| Notifications | In-app inbox via dedicated queue | FCM push adapter behind the same queue, no matching changes |
| Reads | Direct Postgres reads | Cache-aside for hot profiles and saved routes |
| Observability | Structured logs, /health/ready, queue failure hooks | Queue-depth alerts (> 30 s wait), k6 load tests on matching |