Skip to content
Back to Blog
July 28, 2026

The Monorepo Question for Multi-Platform Travel Apps

How we structure code across mobile, web, and backend for maximum code sharing and minimum pain — repository structure, shared types, and CI/CD trade-offs.

The Monorepo Question for Multi-Platform Travel Apps
M

The mobile app defines a Trip type. The web app defines a Trip type. The backend defines a Trip type. They started identical. Three months later, the mobile Trip has a `localDepartureTime` field the web doesn't know about. The web Trip has a `carbonOffset` field the mobile doesn't know about. The backend Trip has both fields plus a `internalNotes` field that should never reach either client. Three definitions of the same concept, drifting apart in three repositories.

Type drift is the quiet infrastructure problem that doesn't trigger alerts, doesn't cause crashes (immediately), and doesn't show up in monitoring. It shows up six months later when a frontend engineer assumes the Trip type matches the backend and builds a feature on a field that doesn't exist in the response they're actually receiving.

Repository structure

Illustration for this section

We run three separate repositories: the mobile app (a cross-platform mobile framework), the web app (the web framework), and the backend (Express.js). Each has its own build system, its own dependencies, its own CI pipeline, and its own deployment. This is a polyrepo structure.

We chose polyrepo for practical reasons. The mobile app builds with the mobile build tooling's build service. The web app builds with the web framework's build system. The backend builds with a single typed language across the stack compilation. These build systems have different requirements, different configurations, and different output formats. Putting them in a single repository would require a monorepo build tool that orchestrates all three, adds complexity to the CI pipeline, and creates coupling between unrelated builds.

The polyrepo structure also matches our deployment pattern. The mobile app deploys to app stores on a weekly cycle. The web app deploys on every merge to main. The backend deploys independently, sometimes multiple times per day for AI agent updates. These deployment cadences are different enough that coupling them in a single repository would create friction.

But polyrepo has a cost: the type drift I described above. Each repository defines its own a single typed language across the stack types for the API contract. There's no shared source of truth that all three consume. The types are manually synchronized, which means they're eventually inconsistent.

Shared a single typed language across the stack types

The ideal solution is a shared types package that all three projects import. One definition of Trip, FlightOption, HotelOption, Booking, and every other entity that crosses the API boundary. Change it once, and all three consumers see the change.

We're moving toward this. The shared types include every entity that appears in the API contract: FlightOption (with fare details, routing, airline information), HotelOption (with property details, rates, amenities), Trip (with status, bookings, documents), Booking (with confirmation, payment status, traveler details), and the standard response wrapper `{ success: true, data: T }`.

The shared package is published and versioned. Each project pins a version of the shared types. When the backend needs to add a field, the developer updates the shared types package, bumps the version, and then updates each consumer project to use the new version.

The versioning creates an intentional friction point. You can't accidentally change a type that three projects depend on. You must publish a new version and each consumer must explicitly adopt it. This friction prevents breaking changes from propagating silently.

The challenge is adoption speed. When the shared types update, all three projects need to update their dependency. If the mobile app is in a release freeze and can't update for two weeks, the shared types and the mobile app's types are out of sync for those two weeks. This is manageable (the mobile app just uses the older version) but requires coordination.

Build system configuration

Supporting diagram

Each platform has its own build tooling, and they don't play well together in a single workspace.

The mobile app uses the mobile build tooling, which has specific requirements for how dependencies are resolved, how native modules are linked, and how the bundle is constructed. the mobile build tooling's build service expects a specific project structure.

The web app uses the web framework 15, which has its own build system, its own module resolution, and its own optimizations (server components, edge runtime, static generation). the web framework expects to be the root-level framework, not a sub-project in a monorepo.

The backend uses standard a single typed language across the stack compilation. It's the simplest build of the three, but it has its own dependency tree and its own output format.

A monorepo tool could orchestrate all three builds. The trade-off is the complexity of configuring and maintaining that orchestration. Package resolution conflicts (the mobile app needs the mobile app framework's React, the web app needs standard React, the backend doesn't need React at all) are a constant source of friction in monorepos with diverse stacks.

We decided the build complexity wasn't worth the type-sharing benefit, especially since the shared types package achieves most of the type-sharing goal without the monorepo overhead.

CI/CD for multi-platform changes

In a polyrepo structure, a backend API change doesn't automatically trigger mobile and web tests. The developer who changes a backend endpoint must know (or remember) to verify that the change doesn't break the mobile and web clients.

We address this with cross-repository CI triggers. When the backend's API types change (detected by a diff in the route response types), the CI pipeline triggers the mobile and web test suites against the updated backend. If either test suite fails, the backend change is flagged.

This cross-repo testing catches contract mismatches before deployment. If the backend removes a field that the mobile app depends on, the mobile test suite fails in the backend's CI pipeline. The developer sees the failure before merging.

The cross-repo triggers add latency to the CI pipeline. Running three test suites instead of one takes longer. We mitigate this by running the cross-repo tests only when API-surface files change. A backend change that only modifies internal logic (no route changes, no response changes) skips the cross-repo tests.

The trade-offs

Monorepo advantages. Single source of truth for shared types. One pull request can change the backend, mobile, and web simultaneously. Atomic changes across all platforms. Easy code sharing beyond just types.

Monorepo costs. Complex build configuration. Large repository size. Long CI times (a change to one platform triggers builds for all). Dependency conflicts between platforms. Tooling overhead.

Polyrepo advantages. Simple builds. Clear ownership. Independent deployment. No dependency conflicts. Fast CI per project.

Polyrepo costs. Type drift. Cross-repo coordination for API changes. No atomic cross-platform changes. Shared code requires a published package.

Neither is universally better. The right choice depends on team size, platform count, deployment cadence, and how much code is actually shared between platforms.

Choose your repository strategy

If you're building a multi-platform product, here's the decision matrix.

Small team (under 5 engineers), two platforms. Monorepo. The overhead is manageable and the type-sharing benefit is significant. You probably don't have the bandwidth to maintain a separate shared types package.

Medium team (5-15 engineers), two or three platforms. Either works. If your platforms share significant code beyond types (shared business logic, shared utilities), monorepo pays off. If they only share types, a polyrepo with a shared types package is simpler.

Large team (15+ engineers), three or more platforms. Polyrepo with shared packages. At this scale, a monorepo's CI pipeline becomes a bottleneck. Teams need independence. Shared packages provide the type safety without the coupling.

Whatever you choose, the non-negotiable is shared type definitions. Whether they live in a monorepo's shared directory or in a published package, the API contract must have a single source of truth. Three independently-maintained copies of the Trip type will drift, and the drift will cost you more than any repository strategy decision.


Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. Plan your next trip.

Share this article

Ready to Plan with Nowah?

Bring the idea. Nowah will help turn it into a trip.

Try Nowah