Taxi Booking Platform
Rider, driver and dispatch — one realtime system
- Role
- Full Stack & Mobile Engineer
- Timeline
- 2024
- Stack
- FlutterDartNode.jsPostgreSQLWebSocketsREST APIs

A local operator needed to move from phone-call dispatch to an app-based service without losing the control their staff had over the fleet. The answer was three coordinated clients on one realtime backbone.
Problem
Bookings arrived by phone, were written into a shared sheet, and were relayed to drivers by call. Under load, the process broke down in predictable ways:
- No live picture of where drivers were or which were free.
- Riders had no visibility — no ETA, no fare estimate, no trip history.
- Dispatch decisions depended on one person's memory of the fleet.
Solution
Three clients on one platform: a rider app for booking and live tracking, a driver app for accepting and running trips, and a web dashboard giving operations a real map of the fleet. Fare estimation, trip state and notifications all flow through a single Node.js API, so every screen agrees on what is happening at any moment.
Architecture
Clients
- Rider app (Flutter)
- Driver app (Flutter)
- Ops dashboard (web)
API layer
- Node.js REST API
- WebSocket gateway
- JWT auth
Core services
- Trip lifecycle
- Driver matching
- Fare engine
- Push notifications
Data
- PostgreSQL
- Geospatial queries
- Trip event log
Trip state lives server-side as a small state machine (requested, assigned, en route, completed, cancelled). Clients subscribe over WebSockets and render whatever the server says — which eliminated a whole class of "my app shows something different" bugs.
Challenges
Location updates without draining batteries
Streaming driver GPS continuously killed devices in hours. The fix was adaptive: high-frequency updates only during an active trip, coarse updates while idle, and server-side interpolation so rider maps still feel live.
Assignment race conditions
Two dispatch paths could grab the same driver at once. Moving assignment into a single transactional operation in PostgreSQL — with row-level locking on the driver record — made double-booking structurally impossible rather than merely unlikely.
Flaky networks mid-trip
Trips continue when phones lose signal. Both apps queue state transitions locally and reconcile against the server's event log on reconnect, with the server as the single source of truth.
Technologies
- Mobile — Flutter and Dart for both rider and driver apps from a shared core package.
- Backend — Node.js REST API plus a WebSocket gateway for live trip and location events.
- Data — PostgreSQL with geospatial queries for nearest-driver matching and a trip event log for auditability.
- Delivery — Environment-based configs and versioned releases for the two store apps.
Screens


Lessons Learned
- Realtime UX is a server-state problem first; the client is a projection of it.
- Battery budget is a feature requirement, not an optimisation pass.
- A boring, explicit state machine beats clever event handling every time a dispute needs to be replayed.