Case study

BizzaTrip API

Category
Backend / Cloud
Period
Oct 2024 – Dec 2024

Multi-day travel itinerary API bridging an ML route optimiser to an Android app

  • Node.js
  • Express.js
  • Firebase Auth
  • Cloud Firestore
  • Google Maps Places API
  • Google Cloud Storage
  • Cloud Run
  • Docker

Context

Planning a multi-day trip is an optimisation problem wearing a planner's clothes. A traveller picks a handful of places, says how many days they have, and expects a sensible order — otherwise they spend the first morning of the trip working out whether they should drive north or south before visiting anything.

This was a Bangkit Academy capstone. The brief was a recommendation service for multi-day itineraries, built as a system rather than a single app: the team split into a machine learning division, a backend division and a mobile division, each publishing its own repository, and the goal was that the three would actually talk to each other.

Problem

The three divisions had to agree on contracts before any of them could build anything useful:

  • The ML division was producing route recommendations, and could not care how they were consumed.
  • The backend division had to expose plans and destinations, validate input, and persist results.
  • The mobile division had to render an itinerary on Android.

The hard part was not any single piece. It was that a single wrong payload shape between two independently developed services turns into a feature that appears broken end to end, and finding out which side is wrong takes longer than building it.

Solution

A Node.js and Express service that sits between the model and the app.

A traveller submits their chosen places, the trip length and their origin. The service validates that input before spending a call on the model, forwards a reduced payload to the ML endpoint, and folds the returned routes back into a day-by-day plan stored against the user.

Everything else in the API follows from that flow: authentication, plan CRUD, and destination lookup backed by Google Maps Places API.

Architecture

Versioned routing. src/versions/v1/ holds the route modules, composed through a nested router, with plans, destinations and auth as separate routers. A later version can sit beside v1 rather than replacing it.

Firebase on both sides. The client SDK and the admin SDK are both initialised: the admin SDK verifies bearer tokens in the auth middleware and gives privileged Firestore access, while the client SDK handles sign-in. Firestore is organised per user, with plans under Users/{uid}/Plans, which makes ownership a property of the document path rather than a query that has to be trusted.

Credentials are validated at boot, not at first use. src/index.js decodes base64 service-account JSON from the environment and throws on any missing required variable. A service that starts and then fails on its first request is much harder to diagnose than one that refuses to start.

Fail loudly on an empty model response. If the ML endpoint returns no routes, the request ends with a 400 and nothing is written, rather than persisting an empty itinerary the user would later find blank.

Containerised for Cloud Run. A Node 20 Alpine image, PORT exposed, started with npm start.

Challenges

Calling another team's service. The ML endpoint is a separate deployment on Cloud Run, and neither team controls its availability or its rate limits. The request payload is reduced to the six fields the model actually needs, so the contract stays narrow and the call stays cheap.

Sharing a contract across divisions. Splitting routes by version and keeping plans, destinations and auth in separate modules meant each division could work against a defined surface instead of an assumption about what the others were building.

Identifying plans without a database sequence. Firestore document IDs are client-supplied, so plan identifiers are generated by reading the user's existing plans and incrementing the highest. It is not elegant, but it is predictable and it keeps plan identity scoped to the user who owns them.

Results

Deployed on Cloud Run, with the service, the ML model and the Android client as independent deployments behind one API contract.

Within the backend division I authored 27 of the repository's 58 commits, working alongside the division lead.

The service exposes nine endpoints covering authentication, full plan CRUD, and destination lookup, with request validation on the plan creation path and bearer token verification on every authenticated route.

Architecture & documentation

03