Antalya Taxi
Taxi and airport transfer platform for Antalya – passenger app, driver app and multilingual website.
- Problem
- Tourists in Antalya face language barriers and unclear prices, especially for airport transfers.
- Solution
- Passenger app, driver app and a multilingual website that pass requests to licensed taxis at the official meter rate.
- Result
- Live since March 2026; passenger and driver app on the App Store; website in five languages.


Screenshots of the live website, taken in October 2026.
What it is
Antalya Taxi connects passengers with licensed taxis in Antalya – at official taximeter rates and with no commission for passengers. Passengers only pay the fare to the driver; the platform earns a dispatch commission that the driver pays for each accepted ride.
Typical routes are transfers from Antalya Airport (AYT) to Belek, Side, Kemer, Lara and Alanya. Passengers send a request in the app, see an approximate fare up front and follow the driver live once the ride is accepted. Website and app are built for international visitors and are multilingual.
What we built
- Passenger app with Flutter – on the App Store
- Driver app “Antalya Partner” with Flutter, on the App Store
- Live driver tracking and in-ride chat between passenger and driver
- Approximate fare shown before the request is sent
- Multilingual website (EN, DE, RU, AR, TR) with destination, price and airport pages
- Backend, dispatch and admin panel of the shared taxi platform (see below)
The platform behind Antalya Taxi
Antalya Taxi runs on a multi-brand taxi platform that we built and run ourselves: one shared backend and one admin panel for several brands and service regions.
Platform in numbers
- 4backend services
- 333GraphQL queries and mutations, plus 3 subscriptions
- 72database migrations in a checksum chain
- 5service regions with their own maps and routing
- 30–40 srestore from backup, measured in a drill
- iOS 15+minimum version for the apps
Production
All brands of the platform run on a single VPS in Frankfurt (6 vCores, 12 GB RAM).
Load test in an isolated environment (8 vCPU, 32 minutes)
1,600 simulated drivers and 1,500 passengers: around 7,000 requests per minute, passenger API p95 under 300 ms, fare calculation 99.996% successful; at peak about 5 busy cores and 3.4 GB of RAM.
Architecture
- Backend in Go (1.24) with four services: admin API, rider API, driver API and a background worker (dispatch, outbox, push, geo, maintenance)
- GraphQL for the apps including realtime subscriptions; REST and GraphQL for the admin panel
- Passenger and driver apps per brand with Flutter on shared packages
Data
- PostgreSQL with PostGIS: zones as polygons, services, pricing, orders, waypoints, audit logs, driver accounts and a commission ledger with auditable, append-only corrections
- Separate geo database (PostGIS + H3) for the place catalogue, search and autocomplete cache, reverse geocoding, route cache and a live driver geo index
- Redis Streams for high-frequency driver locations; Firebase Cloud Messaging with one Firebase project per brand
Own map and routing stack
- Vector maps generated from OpenStreetMap (Protomaps/PMTiles), served from our own server via Nginx byte-range requests with long immutable caching
- Our own map style, self-hosted fonts and icons: no request to an external map service at runtime, no map token, no licence fees
- Routing with Valhalla: one routing graph per service region (currently five), reachable internally only
More details (4)
- Routing fallback chain: own routing → external routing API → straight-line estimate, each step with a timeout and a circuit breaker
- Address search and reverse geocoding via the separate geo database (PostGIS + H3): query cache → place catalogue → provider – free Nominatim by default, paid providers only behind the cache
- Reverse geocoding never returns an empty address; cache entries live for 29 days
- Map releases are dated, verified and switched atomically; the previous release is kept for rollback
Dispatch & realtime
- PostGIS radius search, nearest drivers first (GiST index, KNN query)
- Offers in configurable waves: count, size, interval, radius growth and timeout are set in the admin panel
- Race-safe acceptance with row locks and conditional state transitions – exactly one driver wins
- Drivers without a location update for 10 minutes are set offline automatically, so offers only go to drivers who are actually available
More details (5)
- Idempotent order creation; driver candidates chosen by zone, service, presence, distance and active job
- Scheduled rides
- Transactional outbox/inbox: relayed with SKIP LOCKED, each event published once to Redis; dead-letter table with replay
- GraphQL subscriptions over WebSocket, with missed events caught up after a reconnect; complemented by push notifications
- Admin panel: live request queue and driver map via Server-Sent Events
Security
- Separate token issuers per app (admin, rider, driver): 15-minute access tokens and rotating 30-day refresh tokens, stored hashed
- Role-based permissions enforced in the backend
- Driver identity verification: document OCR, face match against a selfie and a liveness check
- HTTPS only, with certificate pinning (SPKI)
More details (3)
- Passwords with bcrypt and a versioned pepper
- Sensitive configuration sealed with AES-GCM, with key rotation
- Per-endpoint rate limiting in Redis
Quality & operations
- CI on every push: unit tests, dispatch safety contract tests, go vet, secret scanning and govulncheck
- Integration tests on real PostGIS and Redis including schema-drift checks; GraphQL contract tests for the apps
- Backups with pgBackRest (WAL archive, point-in-time recovery), encrypted off-site, 30-day retention; heartbeat monitoring of the backup jobs
Tested restores: logical restore in about 32 s, point-in-time restore in about 39 s
More details (5)
- Deploys via a signed webhook; only the changed part is deployed
- Migrations in a checksum chain – the deploy stops on any mismatch
- Services restarted one by one with readiness checks; Nginx, systemd and PgBouncer
- OpenTelemetry tracing with personal-data masking, Prometheus metrics, Grafana dashboards and alerts
- SLO dashboards, runbooks for incidents and for chaos and resilience testing
Apps (iOS)
- Flutter (Dart 3) with flutter_bloc; MapLibre GL with our own maps
- Shared design system with about 30 components
- Localisation in around ten languages, including right-to-left
- iOS 15+; background location for drivers
- In-ride chat between passenger and driver – in real time, with read receipts and automatic translation
- Hand-off to navigation apps (Apple Maps, Google Maps, Waze)
Admin panel
- Go with server-rendered templates (templ), htmx and Alpine.js; maps with MapLibre and PMTiles, charts with Vega-Lite
- Dispatcher view, live request queue and live driver map
- Zones, services and pricing: base fare, distance per 100 m, time per minute, waiting, minimum fare and zone surcharges along the route
More details (4)
- Driver identity (KYC) review
- Coupons, announcements and bulk push with delivery counters
- App version policies, deployment log and system health
- Support and complaints, SOS
Looking for support for your development team?
Briefly describe your project or open position. In a free introductory call (30 minutes) we clarify whether and how we can help.
Or e-mail us directly: [email protected]