Full-stack ride-hailing product · React · Node.js · Socket.IO
Uber Clone
A two-sided ride-hailing application with separate rider and captain journeys, address search, vehicle-specific estimates, ride lifecycle controls, realtime events, map views, and Razorpay payment verification.

Overview
A two-sided ride-hailing application with separate rider and captain journeys, address search, vehicle-specific estimates, ride lifecycle controls, realtime events, map views, and Razorpay payment verification.
System architecture
A high-level view of the components and boundaries described by the project source and documentation.
User and data flow
Rider searches pickup and destination, requests an estimate, and selects a vehicle
Authenticated API validates the request, creates a pending ride, and notifies connected captains
Captain confirms the pending request; the rider/captain receive a six-digit start OTP
Captain starts with the OTP, ends the ride, and the rider can complete the Razorpay payment flow
Ride lifecycle
The lifecycle is enforced through state checks in the ride service: a captain can only accept a pending request, start an accepted ride assigned to them, and end their ongoing ride.
Rider creates a ride; waiting for captain acceptance
Captain claims the ride; OTP is used to authorize start
Assigned captain starts the ride after OTP verification
Assigned captain ends the active ride
Cancellation branches from pending or accepted; the service rejects cancellation once the ride has moved to ongoing.
Fare model
Estimate = base fare + (distance in km × vehicle rate) + (duration in minutes × vehicle rate). The current distance/time service is a development stub; implementation details are called out below.
| Vehicle | Base | Per kilometre | Per minute |
|---|---|---|---|
| Car / UberGo | ₹50 | ₹15 / km | ₹3 / minute |
| Moto | ₹20 | ₹8 / km | ₹1.50 / minute |
| Auto | ₹30 | ₹10 / km | ₹2 / minute |
Product capabilities
1. Rider application and journey
- Dedicated entry, signup, login, home, active-ride, riding, and logout screens.
- JWT-protected user routes maintain the rider session and profile; map screens combine address search, map display, and browser geolocation.
- Google Places Autocomplete suggests addresses; a coordinate lookup endpoint resolves address strings.
- Request fare estimates for car (UberGo), motorcycle, and auto before creating a ride.
- View a waiting-for-captain state, captain details after acceptance, ride OTP, live-ride interface, and completion/payment state.
- The API exposes the active ride and paginated rider ride history.
2. Captain application and journey
- Separate captain signup/login and route protection; registration captures vehicle color, plate, capacity, and type.
- Captain home presents a map/dashboard, availability controls, incoming ride requests, and ride actions.
- Connected captains receive Socket.IO ride-request events and can confirm a request.
- Captain-only endpoints support starting a ride with its six-digit OTP and ending an ongoing ride.
- Captain ride history and earnings summaries are supported in the backend service layer.
3. Ride lifecycle and state rules
- Ride state is represented as pending, accepted, ongoing, completed, or cancelled.
- A request begins pending; one captain can atomically accept a pending ride, and a captain must own an accepted ride to start or end it.
- Starting requires the ride's six-digit OTP; cancellation is permitted while pending or accepted, not after the ride is ongoing.
- Rider flows prevent creating a second active request while a pending, accepted, or ongoing ride already exists.
- Ride records link the rider and optional captain and retain pickup, destination, fare, status, distance/duration fields, payment identifiers, and an OTP excluded from default query selection.
4. Maps, geocoding, and discovery
- Google Maps JavaScript API renders maps; Places API provides address autocomplete; the browser Geolocation API supplies the user's current map position.
- The backend coordinate service geocodes addresses through Nominatim/OpenStreetMap.
- Captain documents use GeoJSON Point coordinates in [longitude, latitude] order and define a MongoDB 2dsphere index; a radius-search service is present.
- Repository note: the current create-ride controller sends requests to all captains with active socket IDs rather than using the radius-search service. The UI map's location watcher does not currently publish a persisted rider/captain tracking stream through the server.
5. Vehicle pricing and estimates
- Estimate all three vehicle categories from a base fare plus per-kilometer and per-minute rates, rounding the resulting fare to a whole rupee.
- Car (UberGo): ₹50 base + ₹15/km + ₹3/minute.
- Moto: ₹20 base + ₹8/km + ₹1.50/minute.
- Auto: ₹30 base + ₹10/km + ₹2/minute.
- Repository note: the current getDistanceTime service returns a fixed 10 km / 15 minute development response. Estimates therefore demonstrate the fare calculation, but are not route-accurate until a live distance-matrix implementation replaces that stub.
6. Razorpay payment lifecycle
- The backend creates an INR Razorpay order using its server-held key secret and returns the public key/order details to the frontend checkout.
- After checkout, the backend recomputes an HMAC-SHA256 signature from orderId|paymentId and compares it before proceeding.
- The verified-payment route records payment identifiers and creates/notifies a ride; the client includes trip details for that creation flow.
- The application also has a separate ride-creation endpoint without the payment-order verification path, so the payment-first and direct-create flows should be understood as distinct code paths.
- Razorpay private credentials belong only in backend environment configuration; the frontend uses the public key for checkout.
7. Identity and session security
- Rider and captain credentials are stored in separate MongoDB models, with bcrypt password hashing and bcrypt comparison at login.
- Both roles receive signed JWTs with a 24-hour expiry; separate authentication middleware protects rider and captain endpoints.
- Logout stores a token in a blacklist collection whose document expires after 24 hours; password fields are excluded from default model queries.
- Route inputs use express-validator for email, names, passwords, vehicle types, ride IDs, addresses, and OTP length.
8. Realtime events and online state
- Clients join the Socket.IO server with a user ID and role; the server associates their socket ID with the corresponding user or captain record and confirms the join.
- The backend emits new-ride to connected captains, ride-confirmed to the rider, ride-otp to the captain, ride-started to the rider, and ride-ended after completion; ride-cancelled is included in the documented event contract.
- Disconnect handling clears the matching user/captain socket ID and online flag.
- Repository note: the update-location-captain handler currently logs incoming coordinates; it does not persist the new location or broadcast a location update in the current socket implementation.
9. API surface
- Users: register, login, profile, logout.
- Captains: register, login, profile, logout.
- Maps: get-suggestions, get-coordinates, get-distance-time.
- Rides: create-payment-order, verify-payment, create, get-fare, confirm, start-ride, end-ride, active ride, rider/captain history, ride details, cancel, and socket diagnostic.
Data model and domain
MongoDB domain model
User and captain documents connect to rides by ObjectId references. Captain location is indexed as GeoJSON for near queries; JWT revocation records expire through a MongoDB TTL index.
Fast data retrieval and page delivery
This portfolio case study is statically generated at build time, so opening it does not wait for a case-study API or database query. The notes below describe the project's own data path; they distinguish implemented behavior from scale-up opportunities.
Ride-history queries
The API exposes active-ride lookups and paginated rider history, keeping the history response bounded instead of returning an unlimited timeline.
Geospatial index
Captain records define a MongoDB 2dsphere index and the repository contains a radius-search helper; the current create-ride controller does not use that helper and instead notifies all captains with active sockets.
Static portfolio route
This case-study page is pre-rendered at build time. Ride estimates and live app data remain dependent on their external API/database services and are not fetched to display this portfolio page.
HTTP API surface
User auth
POST /users/register · POST /users/login · GET /users/profile · GET /users/logout
Captain auth
POST /captains/register · POST /captains/login · GET /captains/profile · GET /captains/logout
Maps
GET /maps/get-suggestions · GET /maps/get-coordinates · GET /maps/get-distance-time
Rides
POST /rides/create · GET /rides/get-fare · POST /rides/confirm · GET /rides/start-ride · POST /rides/end-ride
Payment and history
POST /rides/create-payment-order · POST /rides/verify-payment · GET /rides/user/active · GET /rides/user/rides · GET /rides/captain/rides · GET /rides/:rideId · POST /rides/cancel
Realtime event contract
| Event | Direction | Purpose |
|---|---|---|
join | Client → server | Register user ID and role for socket routing |
join-confirmed | Server → client | Confirm socket ID and registered role/user |
new-ride | Server → captain | Notify connected captains of a pending ride |
ride-confirmed | Server → rider | Deliver accepted ride and captain details |
ride-otp | Server → captain | Deliver the start-verification OTP after acceptance |
ride-started | Server → rider | Notify rider that the captain has started the ride |
ride-ended | Server → rider | Deliver the completed ride payload |
ride-cancelled | Documented event | Cancellation event in the README event contract |
update-location-captain | Captain → server | Location event is currently logged; it is not persisted or rebroadcast |
Implementation notes and current limits
These details were checked against the repository code as well as its README, so the design goals are not confused with behavior that is already implemented.
- Distance/time is currently a fixed development stub (10 km / 15 minutes), so fare calculations are formula demonstrations rather than route-specific estimates.
- Ride requests are currently sent to every captain with an active socket ID; the 2dsphere radius-search helper is not used by the create-ride controller.
- The frontend map watches browser geolocation locally, but the current client/server code does not implement a complete persisted and broadcast live captain-location stream.
- The backend service generates an OTP with crypto.randomInt, but the confirmation controller replaces it with Math.random before returning it. This implementation detail should be corrected before treating OTP generation as production-grade.
- Payment-order creation currently accepts an amount from the authenticated client; production payment integrity requires deriving and binding the amount to the server-calculated ride rather than trusting client input.
- The ride service/controller read and write vehicleType and payment-related fields with names that do not all match the current Ride Mongoose schema (for example, paymentId vs paymentID). Reconcile the schema and domain contract before relying on those values in persisted ride records.
- The backend README/package describes an API and deployment design, but its npm test script is a placeholder that exits with an error; automated backend tests are not included.
Engineering decisions
Layered backend
Express routes validate and authenticate requests, controllers coordinate transport/realtime/payment behavior, and ride/map services encapsulate domain operations.
Conditional state transitions
Captain acceptance updates only rides still pending; start and end operations check both assigned captain and expected state. OTP is hidden by default in Mongoose selections.
Input validation and role separation
express-validator checks route fields, with distinct JWT middleware for rider and captain access.
Realtime delivery
Socket.IO uses the persisted role-specific socket ID to emit events directly to the rider or captain involved in a workflow.
Operational boundaries
The README's target design and the current implementation differ in a few important places: distance/time is mocked, create-ride dispatch is not radius-filtered, and location socket events are not persisted. These are called out explicitly rather than described as completed production behavior.
Setup and delivery
- Repository layout separates UBER-FRONTEND (Vite SPA) and UBER-BACKEND (Express API/socket server).
- Backend development runs on port 3000; frontend development runs on port 5173.
- Frontend settings: VITE_BASE_URL, VITE_GOOGLE_MAPS_API_KEY, and VITE_RAZORPAY_KEY_ID.
- Backend settings: MONGODB_URI, JWT_SECRET, GOOGLE_MAPS_API, RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET, and PORT.
- The README describes a Vercel SPA deployment with an index.html rewrite and a separately hosted Node API (Render, Railway, Heroku, or EC2 examples).
- Production CORS must allow the deployed frontend origin; MongoDB, JWT signing, Maps, and Razorpay secret values must remain server-side.
- The backend package currently defines its test script as a placeholder that exits with an error; no automated backend test suite is documented there.
Technology
Explore the project
Open the project links above to review its source, documentation, or available deployment.

