Browse Templates/Ride Sharing App

Ride Sharing App

A complete ride-sharing database schema designed for on-demand transportation platforms. Includes rider and driver profiles, ride requests with pickup/dropoff locations and fare calculation, payment processing, and mutual rating systems. Suitable for building apps like Uber, Lyft, or custom ride-hailing solutions.

PostgreSQL5 tablesMobile
ride-sharingtransportationmobilepaymentsratings
Created with ERD Studio

About this schema

rides stores four coordinates and two addresses for one trip. The addresses are what people read and the latitude and longitude pairs are what the system computes with — distance, routing, matching — and keeping both means a receipt stays readable while the geometry stays usable.

ratings carries a from_rider boolean rather than two separate tables. Both parties rate each other after the same trip, so one row with a direction flag covers it — and the aggregate rating columns on riders and drivers are the cached result of those rows.

driver_id is nullable on the ride, which is the whole dispatch model in one column. A ride exists from the moment it is requested, before anyone has accepted it, and assignment is an update rather than a row in a queue.

Key relationships

riders / drivers → rides
Both parties on one row, with the driver nullable because a request exists before it is accepted.
rides → payments
Money attaches to the completed trip, with its own status so a failed card does not undo the ride that happened.
rides → ratings
One table for both directions, with from_rider saying who is rating whom for that trip.
riders.rating / drivers.rating
Cached averages of the ratings rows, read on every match and recomputed rather than treated as the record.
rides.fare / distance_km
Both frozen on completion, so a later change to pricing never restates what a past trip cost.

Design decisions

The fare is stored, not computed

Recalculating from distance and a current rate would restate every historical trip the day pricing changes, and would ignore surge, tolls and promotions that applied at the time. Freezing fare on the ride is what makes a receipt true. distance_km is kept beside it for the same reason — as evidence of how the number was reached, not as an input to recompute it.

Ratings are one table with a direction

Two tables would duplicate every column to express the same event seen from opposite ends. A from_rider flag keeps one row shape and makes a trip's feedback a single read. The cost is that constraints are weaker — nothing but application logic stops two ratings in the same direction for one ride.

Driver availability is a flag

is_available on the driver makes finding someone to dispatch a single indexed read. What it cannot express is why they are unavailable, how long they have been waiting, or where they are — real dispatch needs a continuously updated location with a spatial index, which this schema deliberately leaves out.

Coordinates are plain numeric columns

Separate lat and lng columns are portable and readable anywhere, and they cannot use a spatial index. For storing where a trip began and ended that is fine; for finding the nearest driver it is not, which is why a production system adds a geography type or a geohash while keeping these columns as the trip record.

Tables in this template

ridersRiders
6 cols
driversDrivers
10 cols
ridesRides
14 cols
paymentsPayments
6 cols
ratingsRatings
6 cols

Frequently asked

Why is driver_id nullable?
Because a ride is requested before it is accepted. Requiring a driver would mean inventing a placeholder for every pending request, and every dispatch query would then have to exclude that fake row.
How are ratings aggregated?
The rating columns on riders and drivers are averages of their ratings rows, updated as new ones land. They are a cache — the individual rows remain the record, so an average can always be rebuilt.
Where is surge pricing?
Only in its effect. The fare column holds whatever was charged, but no multiplier or rate is stored, so why a trip cost what it did cannot be explained from the data — a fare breakdown table is what makes that answerable.
Can a ride have several passengers?
Not as modelled; rides names one rider. Shared or pooled rides need passengers as their own rows against a trip, each with their own pickup, dropoff and fare share.
How would driver location tracking fit?
As its own high-volume table of positions over time, or more often as in-memory state that never reaches the database. Writing every position update here would dwarf the rides table within days.