A carpool ride-matching and booking platform — find someone already driving your route, or offer your own ride and split the cost.
Built with Next.js (App Router), TypeScript, MongoDB, and a fully free, open-source mapping stack (no Google Maps, no API keys, no billing account required).
- Auth — signup/login with hashed passwords, session handling via NextAuth (Credentials provider).
- Publish a ride — search for pickup/destination by address, see the actual road route on a live map, set seats/price/date/time.
- Search for a ride — real geospatial matching: checks proximity to your pickup and your destination, in the correct direction along the driver's route, not just "same city."
- Book a seat — atomic seat reservation (prevents two riders grabbing the last seat in a race condition), fare computed from actual distance, with cancellation support that restores seats.
| Concern | Tool | Why |
|---|---|---|
| Framework | Next.js 14 (App Router) | Route Handlers double as the backend — one app, one deploy |
| Language | TypeScript | Shared types between API routes and components catch data-shape mismatches at compile time |
| Database | MongoDB + Mongoose | Native geospatial (2dsphere) indexing for route matching |
| Auth | NextAuth.js | Session/JWT handling, no hand-rolled cookie logic |
| Styling | Tailwind CSS v4 | Custom design tokens (see app/globals.css) |
| Address search | Nominatim | Free, OpenStreetMap-based geocoding, no API key |
| Routing | OSRM | Free, OpenStreetMap-based road routing, no API key |
| Map display | MapLibre GL JS + OpenFreeMap | Free map tiles, no API key, no usage limits |
-
Install dependencies:
npm install
-
Set up MongoDB Atlas (free M0 tier): create a cluster, a database user, and allow network access. Grab your connection string.
Note: if your network blocks DNS SRV lookups (some ISPs/routers do), use the non-SRV connection string format instead of
mongodb+srv://— see.env.examplefor the shape. -
Copy
.env.exampleto.env.localand fill in:MONGODB_URI=... NEXTAUTH_SECRET=... # generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" NEXTAUTH_URL=http://localhost:3000 -
Run the dev server:
npm run dev
app/
api/
auth/
signup/ → create account
[...nextauth]/ → NextAuth handler (login/session)
rides/
publish/ → create a Ride + its DriverRoutePoint trail
find/ → the matching algorithm
bookings/
create/ → atomic seat booking
[id]/cancel/ → cancel + restore seats
publish/ → driver: publish-a-ride page (map + form)
search/ → rider: search + results page
login/, signup/ → auth pages
page.tsx → homepage
components/
LocationSearch.tsx → debounced Nominatim address autocomplete
RouteMap.tsx → MapLibre + OpenFreeMap map display
RideCard.tsx → search result card (booking UI)
lib/
db.ts → Mongoose connection (cached across hot reloads)
geocode.ts → Nominatim + OSRM helpers
geo.ts → Haversine distance
authOptions.ts → NextAuth config
models/ → Ride, Booking, User, DriverRoutePoint (Mongoose schemas)
types/index.ts → shared TypeScript contracts used by BOTH API routes and components
- Pickup match —
DriverRoutePoint.find()with MongoDB's$nearon a2dsphereindex, filtered by date. Returns route points within a radius (default 2km) of the rider's pickup. - Destination match — for each candidate ride, check whether that same ride also has a route point near the rider's destination, at a later sequence number. This is what stops a driver heading the opposite direction from being matched just because they pass near the pickup.
- Filtering — only rides with
seatsAvailable > 0andstatus: "active"are returned. - Scoring — a 0–100 match score based on total distance (pickup + destination proximity), sorted best-first.
No external routing service is called at search time — matching is pure MongoDB geo queries against pre-computed route points, so it stays fast regardless of how the route was originally generated.
types/index.ts is imported by both the API route handlers and the React
components. This exists specifically to prevent frontend and backend from
silently disagreeing about data shape — a real bug in an earlier version of
this project, where the search endpoint returned bare ID strings but the UI
expected full ride objects with driver info, price, and distance fields.
- Auth (signup, login, session)
- Publish-a-ride flow with live route map
- Ride matching algorithm (geo-proximity + route-direction filtering)
- Rider search UI
- Booking flow (atomic seat reservation, cancellation)
- Draggable pin for precise pickup/drop-off (address search alone isn't always accurate for specific buildings)
- Driver identity verification (document upload + review flow) —
User.verificationStatusfield already exists, ready to gate on - "My rides" / "My bookings" dashboard
- Dockerize