MOBILE + BACKEND SYSTEM

MetroNexis

Production-Grade Metro Navigation Platform for India

INTERCHANGEORIGINDESTINATION290 STATIONS · BFS + DIJKSTRA
Stations modeled
290
Automated tests
152+
Flyway migrations
18
Cities live
Delhi + HYD

The Problem

Around 75% of Delhi Metro runs underground, where signal is unreliable. MetroNexis is designed to keep route planning available even when the network is offline or degraded — answering 'fastest route, exact fare, where do I change lines?' instantly, in all three network states.

Architecture & System Design

  1. 01

    Mobile app uses React Native + Expo with local MMKV storage and offline BFS/Dijkstra fallback — full route computation runs on-device with no network dependency.

  2. 02

    Backend uses Spring Boot 3.3.5 with layered REST APIs, service layer, PostgreSQL 16, Redis 7, Flyway migrations, JWT authentication, and Swagger/OpenAPI documentation.

  3. 03

    3-tier network awareness: online (API-first), degraded (1.5s timeout then local fallback), and offline (fully local graph from MMKV cache).

  4. 04

    Redis key patterns cover route cache, station cache, fare cache, and service-alert cache with TTL and invalidation strategy to keep data fresh without hammering the database.

  5. 05

    Multi-city architecture isolates Delhi and Hyderabad route data and fare rules — adding a city is a data migration, not a code change.

  6. 06

    Ticket QR payloads are HMAC-signed server-side to prevent forged or replayed tickets, with server-side validation on scan.

Engineering Decisions

Graph in the client, truth in the server

Routing runs entirely on-device for zero-latency, zero-connectivity results. The server is the canonical source that versions and patches the dataset. Chosen because the graph is small (~290 nodes) and the environment is underground/offline-hostile.

Interchange penalty as a first-class edge weight

Early versions returned technically-shortest routes with three line changes. Encoding interchange cost into the graph weights — rather than post-filtering — let the same Dijkstra pass produce genuinely better routes without any UI hacks.

City as a data package, not a code branch

Each city is a self-contained dataset (graph + fare slabs + line metadata) validated against one schema and deployed via Flyway migrations. Adding a city is a data PR, not a feature branch — no business logic changes required.

Tech Stack

Java 21Spring Boot 3.3.5PostgreSQL 16Redis 7React Native 0.81.5Expo SDK 54TypeScriptMMKVDockerRailwayGitHub ActionsSwagger / OpenAPIFlywayJWT

Lessons Learned

  • Shortest path is not best path — user-perceived cost (interchanges, crowding) must live in the graph model, not in UI filtering.
  • Offline-first inverts your sync design: stop asking 'how do I fetch?' and start asking 'how do I reconcile?' with bounded local state.
  • 152 passing tests across auth, fares, tickets, wallet, journeys, and multi-city routing gave real confidence to deploy — not just coverage numbers.

Want the full system walkthrough?

Get in touch