Adarsh Kumar

Hey, I'm Adarsh

Software Engineer making the complex feel simple—and the useful feel exceptional.

Back to experience

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.

Uber Clone ride-hailing project banner from the repository README
Project banner from the repository README.

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.

Rider appReact 19 · Vite · React Router 7
Captain appRole-specific dashboard and ride workflows
API / realtimeExpress · REST · Socket.IO
PersistenceMongoDB Atlas · Mongoose
MappingGoogle Maps · Places · browser geolocation
PaymentsRazorpay · INR · server-side signature verification
Client runtimeVite dev server · Vercel SPA deployment
Server runtimeNode.js · Express · configurable Node hosting

System architecture

A high-level view of the components and boundaries described by the project source and documentation.

01
Rider and captain browser clientsReact + Vite · protected routes · role contexts · maps · ride panels
02
HTTP application APIExpress · express-validator · JWT role middleware · controllers
03
Realtime channelSocket.IO · identity/type join · online socket lookup · ride events
04
Domain services and persistenceRide/fare logic · user/captain/ride Mongoose models · MongoDB
05
External servicesGoogle Places · Nominatim geocoding · Razorpay order and checkout

User and data flow

1Find a route

Rider searches pickup and destination, requests an estimate, and selects a vehicle

2Request a ride

Authenticated API validates the request, creates a pending ride, and notifies connected captains

3Accept and verify

Captain confirms the pending request; the rider/captain receive a six-digit start OTP

4Ride and settle

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.

1
pending

Rider creates a ride; waiting for captain acceptance

2
accepted

Captain claims the ride; OTP is used to authorize start

3
ongoing

Assigned captain starts the ride after OTP verification

4
completed

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.

VehicleBasePer kilometrePer 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

UserName · unique email · bcrypt password · socket ID
CaptainIdentity · active/inactive state · vehicle · GeoJSON location
RideRider/captain refs · pickup/destination · fare · state · OTP · payment IDs
BlacklistedTokenUnique revoked JWT · createdAt TTL after 24 hours

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

EventDirectionPurpose
joinClient → serverRegister user ID and role for socket routing
join-confirmedServer → clientConfirm socket ID and registered role/user
new-rideServer → captainNotify connected captains of a pending ride
ride-confirmedServer → riderDeliver accepted ride and captain details
ride-otpServer → captainDeliver the start-verification OTP after acceptance
ride-startedServer → riderNotify rider that the captain has started the ride
ride-endedServer → riderDeliver the completed ride payload
ride-cancelledDocumented eventCancellation event in the README event contract
update-location-captainCaptain → serverLocation 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

React 19.1Vite 7.1Tailwind CSS 4.1React Router 7.9GSAP 3.14Axios 1.12Google Maps JavaScript APIGoogle Places APIBrowser Geolocation APINode.jsExpress 4express-validatorMongoDB AtlasMongoose 8Socket.IO 4JSON Web TokensbcryptRazorpayNominatim / OpenStreetMapGeoJSONMongoDB 2dsphere index

Explore the project

Open the project links above to review its source, documentation, or available deployment.

© 2026 Adarsh Kumar