The problem
A taxi service that takes bookings from an app needs much more than a booking form. It has to find a free driver nearby, offer the ride, wait for an answer and move on if nobody accepts, within seconds. Rome adds its own rules: fixed city-approved fares for trips to and from the airports and the port. The client needed the full chain, from dispatch to payment.
What I built
I built a real-time booking and dispatch platform with one back end, three mobile apps and a web admin dashboard.
- Dispatch engine — it looks for approved, available drivers who sent a position in the last 60 seconds, within a search radius (8 km by default). It ranks up to 10 of them by real driving time in current traffic from Google, then offers the ride to one driver at a time with a 20-second window to accept.
- Rome fare rules — the pricing service recognises 9 key places, such as Fiumicino airport and the historic centre. It applies the 12 fixed fares set by Roma Capitale (resolution 252/2024). Other trips are priced from the Google route, plus luggage and passenger supplements.
- Three apps — a passenger app, a driver app and an in-car tablet app that shows the route and partner banners. All are built with React Native and Expo, for Android and the web.
- Live tracking and calls — Socket.IO pushes driver positions and ride status. Passenger and driver can call each other inside the app over WebRTC, without seeing each other’s phone number.
- Payments — Stripe with signature-checked webhooks, and PayPal orders. Payment keys are set in the admin panel and stored encrypted.
- Driver onboarding — drivers upload their licence, documents and photo. An admin approves them before they can go online.
- Sponsors and coupons — a sponsor portal with three plans and coupons with anti-fraud tracking, plus scheduled rides and reviews.
Built with
The back end runs on Node.js 22 with TypeScript, Express and Socket.IO. Data lives in MariaDB/MySQL, and the schema has grown through 24 versioned SQL migrations. Zod validates inputs; logins use JWT and Google sign-in. Google Maps handles routes, travel times and geocoding. The apps use Expo and React Native.
Why it was done this way
Two passengers must never get the same driver. When a driver accepts, the engine claims them with a single conditional update: busy, but only if still available. If no row changes, the driver was just taken and the ride goes to the next candidate. Candidates are ranked by driving time, not straight-line distance, because in Rome the closest car is often not the fastest. If Google does not respond, the ranking falls back to distance.


