/WORK / Carpooling and shared mobility
Shared mobility
Three clients, one ride model, and a fare the server owns.
This is a shared-mobility product for Assam. A driver can publish a trip and a passenger can book a seat. A passenger can publish a request and a driver can offer. On mobile, a passenger can also start an Instant ride and wait for a nearby host. The web application explains the product and carries the scheduled passenger journeys. It does not carry Instant rides or driver tools.
- Category
- Carpooling and shared mobility
- People
- Passengers searching for a seat or posting a trip request
- Drivers listing trips, vehicles, and Instant host jobs
- Visitors reading the public site
- Contribution
- My work on this product is the frontend: the passenger app, the partner app, the Next.js site, and the shared mobile library those native apps use. The API is the service those clients call.
- Status
- Live
The challenge
Shared rides are not one screen with the city name swapped. A listed trip, a passenger request, and an on-demand Instant ride are different agreements. Each one needs its own status, its own people, and a payment step that does not invent the price.
The same person should not get the driver tools and the passenger tools in one binary. Store listings, permissions, and navigation stay clearer when the role is fixed by the app. The public site still has to explain both sides, and it has to let a passenger search and book without installing anything.
The solution
The passenger Expo app is the full rider client. Search, booking, trip requests, Instant, safety, notifications, and profile live there. The partner Expo app is the driver client: listings, vehicles, licenses, offers, and Instant jobs. Both call the same HTTP API.
A shared JavaScript library supplies payments, maps, validation, push routing, and common screens to those two apps. The Next.js application does not import that library. It renders the marketing site and the passenger workflows that belong on the web: discovery, booking, trip requests, profile, help, and support cases.
- Scheduled search and booking on mobile and on the web.
- Passenger-created ride requests, with driver offers reviewed in the product.
- Instant rides and driver operations on mobile only. The web routes for those jobs stop at an app gate.
- A booking guarantee paid through Razorpay Checkout. The amount comes from a server fare preview.
- Location, live maps, trusted contacts, and an SOS path on the native apps.
- Public marketing pages, including corridor and FAQ pages aimed at search.
- Assamese as the default mobile language, with English as the fallback. The marketing site is English.
System architecture
The native apps are React Native programs on Expo. Each one has its own React Navigation tree and its own Redux Toolkit store. Logout resets that store. Tokens, language, and theme are kept in AsyncStorage, and many requests read the token from there.
Shared code is a local package resolved by Metro. Factories let each app pass in its own API paths and auth helpers. Platform conditions pick a native map entry or a browser map entry. The web app is a typed Next.js App Router project. Marketing routes are static. Product routes are client components that call the API with axios and a cookie session.
The API is the source of truth for fares, booking status, and seat inventory. Clients render status and poll location. They do not decide the price. Razorpay Checkout, map providers, route geometry, and Firebase Cloud Messaging sit beside that API. The clients do not call the SMS vendor. OTP is submitted to the API.
How the clients, shared code, and API relate. Read the columns from left to right.
01 Clients
- Passenger appExpo. Search, booking, requests, Instant, safety.
- Partner appExpo. Listings, vehicles, offers, Instant jobs.
- Web appNext.js. Marketing, search, booking, requests. No Instant or driver tools.
02 Shared mobile library
- Common modulesCheckout, maps, validation, push routing, shared screens.
- Not used by webNative modules stay out of the Next.js bundle.
03 API
- HTTP APIAccounts, listings, requests, offers, fare preview, booking status.
- AuthToken scheme on the clients. A 401 clears the mobile session.
04 Services the clients call
- RazorpayCheckout for the booking guarantee after the server names the amount.
- MapsGoogle Maps when a key is present. Leaflet and OpenStreetMap otherwise. OSRM for the route line.
- PushFirebase Cloud Messaging on Android, with routing shared by both apps.
A location service sits behind search. The diagram stops at the services the clients actually call. It does not describe private hosts or credentials.
Key engineering decisions
A shared native core, and a web app that is not React Native
Engineering challenge
Payments, maps, SOS, and validation are the same product idea in two Expo apps. Those modules depend on native APIs that do not belong in a Next.js bundle.
Implementation approach
Both Expo apps depend on one local package, with subpath exports and Metro aliases. Shared factories receive each app's API paths. The web app mirrors a few pure values, such as brand tokens, and keeps its own axios client.
Engineering rationale
The native apps can move together without pretending the website is a third React Native binary. Marketing pages stay static and typed.
Trade offs
Path lists and error parsing can drift, because there is no shared TypeScript contract package. The web client stays smaller and free of native modules.
The server names the fare
Engineering challenge
A booking guarantee has to match refunds and later capture. A client that multiplies distance by a rate will disagree with the ledger.
Implementation approach
The client asks for a fare preview, creates an order, opens Razorpay Checkout, verifies the payment, and then sends the domain action with the payment id. The same handoff sits in front of booking, accepting an offer, and confirming an Instant ride.
Engineering rationale
The amount the person confirms is the amount the API already computed. The client owns the handoff, not the price.
Trade offs
The flow has more round trips, and a UPI app can interrupt Checkout. Fulfillment can also complete from the server side if the app does not stay in the foreground.
One role per binary
Engineering challenge
Passenger and driver journeys share an account system and a push channel, but they should not share a tab bar. A role switch inside one app hides the wrong tools at the wrong time.
Implementation approach
Each native app hard-codes its role. Passenger tabs are Home, Request, and Profile. Partner tabs are Home, My Rides, and Profile. Stacks, listings, and Instant jobs differ. Push routing is shared so a notification can be resolved in the app that should open it.
Engineering rationale
Permissions and store listings match the binary. A passenger build cannot render the driver listing flow.
Trade offs
Login, profile, and navigation boilerplate exist twice. That duplication is the cost of keeping the two products obvious.
Maps that still draw when a Google key is absent
Engineering challenge
Live trip maps are part of the ride, and a missing maps key should not blank the screen. Location text inserted into a WebView is also a safety problem.
Implementation approach
Native builds use the Google provider when configured. Otherwise the shared map uses Leaflet and OpenStreetMap inside a WebView. Route geometry comes from OSRM. HTML is sanitized before injection. Foreground driver position is polled.
Engineering rationale
The map path can ship in more than one build environment, and untrusted location fields are not written straight into the page.
Trade offs
Two map implementations have to stay in step, and polling is coarser than a live socket. The choice favors a working map over one provider.
Core capabilities
Scheduled ride discovery and booking
A passenger searches listed trips, opens one, and books a seat. The same journey exists on mobile and on the web.
Value. Someone can take a planned shared ride without waiting for an on-demand host.
Complexity. The booking step waits on a server fare preview and a payment verification before the seat action is sent.
Passenger requests and driver offers
When no listing fits, a passenger posts a trip request. Drivers respond with offers. The passenger reviews those offers.
Value. The product covers empty search results instead of ending the journey there.
Complexity. This is a second domain beside driver-created trips. The clients keep the request and the listing as separate flows.
Instant rides on mobile
The passenger home can start an on-demand ride from a map. A host accepts in the partner app. The web route for Instant does not run that flow.
Value. On-demand trips stay available where location and the host handshake are practical.
Complexity. The home screen holds map state, request state, and the later payment confirmation. The website stops at a gate.
Driver listings and vehicles
The partner app registers vehicles and licenses, publishes listings, and manages the trips a driver is offering.
Value. Supply can be created by the person who has the car, in a client built for that job.
Complexity. Listing, vehicle, and offer screens are partner-only stack routes, not a mode inside the passenger app.
Safety and notifications
Both native apps include help, trusted contacts, notifications, and an SOS screen. Background location updates are part of the native SOS path.
Value. A shared ride has a way to reach help without leaving the product.
Complexity. Push routing is shared, while the screen that opens still depends on which binary received the message.
Public marketing and search pages
The Next.js site publishes the product story, how it works, safety, routes, and a Guwahati corridor page. Several of those routes are static.
Value. A person can understand the product and land from search before creating an account.
Complexity. Marketing stays server-rendered and English, while the installed apps default to Assamese.
Technology stack
Mobile
- React Native
- Expo
- React Navigation
- Redux Toolkit
- i18next
- AsyncStorage
Web
- Next.js
- React
- TypeScript
- Tailwind CSS
- Redux Toolkit
- axios
Shared mobile library
- JavaScript package
- Metro subpath exports
- Shared validation helper
Web forms
- React Hook Form
- Zod on authentication flows
Integrations
- HTTP API
- Razorpay Checkout
- Firebase Cloud Messaging
- Google Maps
- OpenStreetMap and Leaflet
- OSRM
Testing and delivery
- Jest and Testing Library on the mobile apps
- ESLint on the web app
Engineering impact
Two installable clients and a website can change on different schedules while still talking about the same trip. The shared library is the place where native behavior stays consistent.
Passenger journeys that belong on the web are real API workflows, not a brochure with a dead button. Journeys that need the device stay on the device, with an explicit gate instead of a half-built web screen.
Payment UI cannot drift into its own price list. The preview, the Checkout session, and the booking action share one server amount.
Language is treated as a product choice. Riders open the app in Assamese. The public site can still be indexed in English.
Workflows and modules
These drawings describe structure. They are not captures of the passenger app, the partner app, or the website.
Passenger
- Home
- Search and trip detail
- Booking
- Trip requests
- Instant
- Profile
Partner
- Listings
- Vehicles and license
- Offers
- Instant jobs
Shared
- Checkout
- Maps
- SOS
- Push routing
- Validation
Web
- Marketing pages
- Ride search
- Booking
- Requests
- Profile and help
- 01Find or request
Search a listed trip, or post a request when nothing fits.
- 02Confirm the amount
The API returns the fare. The client opens Checkout with that amount.
- 03Book or accept
After verification, the client sends the booking or the accepted offer.
- 04Ride
On mobile, maps and safety stay available. Instant hosting stays in the partner app.
Engineering takeaways
- I keep clients that share a product from sharing a runtime they cannot both run.
- I leave price and seat inventory on the server, and I treat the client as the handoff.
- I would rather gate a mobile-only journey than pretend the website can host it.