krishna@
~/projects
liveproduct · 2024

Easy Go Tours

Intercity ride-sharing for Nepal.

Easy Go Tours — preview
01 // overview

Three-app system — Flutter passenger app, Next.js admin, NestJS backend — connecting riders with vetted drivers for intercity routes. I led engineering: owned the backend end-to-end, mentored the Flutter and Next.js teams.

02 // the.story

the gap in the market

If you've taken intercity transport in Nepal you know the menu. There are tourist-class buses — cheap, slow, eight hours from Pokhara to Kathmandu, no AC, the road is what it is. There are private cars hired through a friend-of-a-friend, comfortable but expensive and entirely dependent on who you know. There's no obvious middle: a car-sized service with the booking ergonomics of a real app.

That gap is the product. Easy Go Tours pairs passengers with vetted drivers for intercity routes — Pokhara to Kathmandu, Kathmandu to Chitwan, the standard tourist circuits, plus everywhere in between — with prices set up-front and zero haggling. You open the app, pick origin and destination, see a fare, book. The driver shows up.

why this is harder than it looks

Building a ride-sharing app for intercity travel in Nepal isn't a "Nepali version of Uber" problem. The constraints are different enough that some of Uber's solutions are wrong here.

Terrain pricing. A Pokhara → Kathmandu ride isn't 200km of flat highway, it's 200km of mountain road that takes 6-8 hours and burns 1.5x the fuel of a coastal route. Distance-based pricing under-quotes; time-based pricing over-quotes when the road is good. The app uses a route-aware model that factors elevation gain, expected drive time, and seasonal road conditions.

Driver vetting. Drivers aren't anonymous gig workers — for intercity, you want a known operator with a clean vehicle, valid insurance, and a route history. The vetting flow is deliberate: vehicle inspection photos, registration check, insurance verification, and a probationary period before the driver appears in search.

Localization is not just translation. The app ships in Nepali first. Every fare prompt, every confirmation, every error message reads naturally in Nepali because most of our users default to it. English is a fallback, not the canonical version. (This is the inverse of how most "internationalized" apps treat Nepali — strings that read like Google Translate output, because they are.)

Referrals because word-of-mouth. Network growth here happens through families and friend groups, not paid social. The referral program is one of the highest-retention features: every existing user can give credit to a new user, both sides benefit.

my role

I led engineering on this product. That meant three things:

  1. Owned the backend end-to-end. A real-time API with auth, role-based permissions, ride matching, booking lifecycle, driver wallets, push notifications, document uploads, and observability. Architecture, schema, code, deploys — all me.
  2. Mentored the mobile team. The passenger app is Flutter. I didn't write the UI, but I designed the API contracts the app consumed, paired on the trickier real-time flows, and ran code review.
  3. Mentored the admin team. The dispatcher console is a web app. Same pattern — contracts, review, paired debugging on permissions and live updates.

The two app teams were earlier in their careers; the backend was where I had nobody to lean on, so most of what I'd say about this project is about that side.

what kind of problems

Without going into the specifics, the problem shape is what makes this interesting:

  • Real-time, multi-actor. A passenger requests, multiple drivers see the request, the first to accept wins atomically, everyone else gets told the request is gone. Getting that race correct at the database level — not in application code — is the difference between a system that works at 10 requests a day and one that works at 10,000.
  • State machines with side effects. A booking doesn't just have a status; it has transitions, and each transition needs to fire notifications, move balances, write audit logs. I leaned on event-driven patterns so each side effect is an isolated handler rather than spaghetti inside a service method.
  • Time-sensitive flows that survive deploys. Pending timeouts ("driver has 30 seconds to accept") can't live as in-process timers — they vanish on restart. Durable job queues backed by Redis handle this cleanly.
  • Permissions that match the org chart. Dispatchers, finance staff, support agents, superadmins — each sees a different slice. The same ability definitions drive both the API guards and the UI gating, so the front end never shows a button that 403s when clicked.
  • Stateless, horizontally scalable. The API and the real-time gateway run in the same process but hold no in-memory state. Sessions, queues, and real-time fan-out all live in shared infrastructure, which means N instances behind a load balancer with no sticky-session gymnastics.

what mentoring actually looked like

Concretely:

  • Contracts before code. Before either app team started a feature, I wrote the request/response shapes in OpenAPI and we agreed on them in review. The mobile and admin teams could mock against the spec while I built the endpoint, so nobody waited.
  • Pair-debugging the live flows. Real-time is where less-experienced devs lose days — wrong room, missed events, missing reconnect logic. I sat with each team through one full booking lifecycle on a screenshare, watching events on both ends until the model clicked.
  • Code review with reasoning. Not "change this to that" — "change this to that because…" People grow from explanations, not rubber stamps.
  • Off-loading triage. At first I was the one getting paged. I trained the mobile and admin leads on how to read traces and our state logs so they could triage their side without escalating, which freed me to focus on the harder problems.

the stack, in shape

  • Backend: Node.js with a typed framework, Prisma + PostgreSQL, Redis, durable job queues, real-time gateway, S3-backed storage with presigned URLs, error and performance observability, declarative authorization.
  • Admin: A modern React/Next.js front end with typed state and a permissions layer that mirrors the backend's.
  • Passenger app: Flutter, talking to the same backend over HTTP and WebSocket, with native push for the times the socket isn't enough.

what's next

The roadmap is the boring kind: more routes, more cities, deeper hotel integrations. The interesting problem on the horizon is predictive demand pricing for festival travel (Dashain, Tihar), where demand spikes for a few days and a naive surge multiplier breaks the social contract. We're prototyping a transparent "festival rate" announced in advance rather than dynamically computed at booking time.

If you need an intercity ride in Nepal, the app is in the App Store. If you want the engineering takeaway, it's the boring one: choose primitives that survive a deploy, push the hard concurrency to the database, write contracts before code, and mentor by explanation, not edict.