How I built a ride-hailing app without a Google Maps API key.

TapRide is a full-stack ride-hailing app — riders request a trip, watch the driver approach on a live map, agree a fare up front and chat in-app. The interesting constraint I set myself was simple: no Google Maps API key, and no PostGIS. Here is how that shaped the whole architecture.

Why avoid Google Maps in the first place?

Every ride-hailing tutorial reaches for the Google Maps Platform on the first day, and then the billing alerts start. Maps, Directions and Distance Matrix all meter per request, and a ride app hammers them — every location update, every fare estimate, every map pan. For a project meant to run as a free, public demo out of Zimbabwe, a metered map bill was a non-starter.

So the rule became: build a real, production-shaped ride-hailing experience on entirely open infrastructure. That single decision turned out to be the most useful design forcing function in the project.

The map layer: OpenStreetMap + Leaflet

On the web app the map is Leaflet rendering OpenStreetMap tiles. On Android the native app uses osmdroid, the OSM renderer for Android. Neither needs an API key. The rider and driver markers are plain Leaflet markers whose coordinates are driven by live data, so the "watch your driver approach" effect is just a marker whose lat/lng updates as new positions arrive.

The web client is React 18 + TypeScript + Vite + Tailwind CSS. The Android client is genuinely native — Kotlin + Jetpack Compose in an MVVM structure — rather than a webview wrapper, so the mobile experience feels like a real app and can talk to location services directly.

Finding nearby drivers without a geospatial database

The classic way to answer "which drivers are near this rider?" is PostGIS with a ST_DWithin query. I didn't want that dependency, so TapRide does proximity in two cheap steps:

  1. Bounding box first. Given the rider's position and a search radius, compute a small latitude/longitude rectangle and query only drivers inside it. That is a plain indexed range query — fast, and supported by any database.
  2. Haversine second. The bounding box returns a handful of candidates, so the app then applies the haversine formula in code to get true great-circle distance and sorts by it. Running haversine over five candidates is free; running it over every driver in the country would not be.

This box-then-refine pattern is a good reminder that you rarely need the heavyweight tool. A coarse, index-friendly filter followed by an exact in-memory calculation covers the vast majority of "find things near me" features.

Real-time everything with Appwrite

The backend is Appwrite — authentication, the database, storage and a Realtime WebSocket. The data model is small and readable:

  • profiles — user profile, role (rider/driver) and vehicle info
  • rides — the lifecycle document for a single trip
  • driver_locations — live GPS positions, updated as drivers move
  • messages — in-ride chat
  • ratings — post-ride ratings

The whole live experience comes from subscribing to those collections over Appwrite Realtime. When a driver's app writes a new position to driver_locations, the rider's subscribed client receives it over the WebSocket and moves the marker. When the ride status changes from requested to accepted, both sides update instantly. Chat is the same mechanism pointed at the messages collection — so rider and driver coordinate the exact pickup without exchanging phone numbers.

Fares agreed up front

A lot of ride apps quote a range and surprise you at the end. TapRide estimates the fare from distance and route before the trip is confirmed and shows it as a single number the rider accepts. Because distance is already being computed for matching, the fare estimate reuses the same maths — no extra paid Distance Matrix call.

One product, two real clients

Keeping a React web app and a native Kotlin app in sync sounds like double the work, and it is more work — but sharing one Appwrite backend means the contract is the database schema, not a shared UI framework. Both clients read and write the same documents and subscribe to the same Realtime channels. The web app deploys to GitHub Pages on push; the Android app is built into an APK by GitHub Actions and attached to a Release.

What I'd tell someone starting this

  • Pick your constraint early. "No Google Maps key" decided the map stack, the proximity algorithm and even the cost model in one move.
  • Model the ride as a single document with a status field. Almost every feature — matching, tracking, chat, rating — hangs off that lifecycle.
  • Let the database be the contract between clients instead of trying to share UI code across web and native.
Related
→ TapRide project overview → Try the live demo → Building a banking app with React and Supabase
Can you build a ride-hailing app without Google Maps?

Yes. TapRide uses OpenStreetMap tiles with Leaflet on the web and osmdroid on Android. There is no Google Maps API key and no map billing — the only cost is whichever tile server you point at.

How do you find nearby drivers without PostGIS?

A cheap latitude/longitude bounding box query narrows candidates to a small rectangle, then the haversine formula refines that shortlist to true distance in application code. No geospatial database extension required.

What makes the tracking and chat real-time?

Appwrite's Realtime WebSocket. Clients subscribe to the driver-locations, rides and messages collections, so position updates, status changes and chat all arrive the moment they're written.