---
title: The Mobile-Backend Contract for Travel Booking
description: "How we keep the mobile app and backend API in sync as both evolve rapidly — API contracts, backward compatibility, breaking change detection, and version enforcement."
canonical: https://nowah.xyz/blog/mobile-backend-contract
lastModified: "2026-08-07T03:53:08.468Z"
---

# The Mobile-Backend Contract for Travel Booking

How we keep the mobile app and backend API in sync as both evolve rapidly — API contracts, backward compatibility, breaking change detection, and version enforcement.

The backend shipped a breaking API change on a Thursday afternoon\. The Trip response object changed a field name from \`departureDate\` to \`departure\_date\`\. A clean, reasonable change in isolation\. But 50,000 mobile users were running app versions that expected \`departureDate\`\. Their trip lists broke\. The trips screen showed empty states\. The fix was a 30\-minute backend rollback, but the damage was a flood of support tickets and a lesson we haven't forgotten\.

Web applications can be deployed and updated instantly. Mobile apps cannot. When you ship a web change, every user gets the new version on their next page load. When you ship a mobile change, users update on their own schedule. Some update within hours. Some update within weeks. Some never update until their phone forces them.

This fundamental asymmetry means the backend must maintain backward compatibility with every mobile app version that's in active use. The contract between mobile and backend isn't just a type definition. It's a commitment to not break old clients.

## Shared type definitions

![Illustration for this section](https://pics.nowah.xyz/website-media/infrastructure-057-img-1.webp)

Our API contract is defined in a single typed language across the stack types that both the mobile client and the backend reference. The types define every entity that crosses the API boundary: FlightOption, HotelOption, Trip, Booking, ChatMessage, User, and their related types.

On the mobile side, the API client uses these types to parse responses. The client knows that a Trip has an id, a destination, a status, and an array of bookings. a single typed language across the stack enforces this at compile time. If the response doesn't match the type, the compiler catches it during the build.

On the backend side, the API routes return objects that conform to these types. The response serialization ensures that the shape matches what clients expect. If a developer changes the Trip model in the database, the serialization layer maps it to the client-facing type, maintaining the contract regardless of internal changes.

The standard response wrapper \`\{ success: true, data: T \}\` adds another layer of consistency\. Every API call returns the same wrapper\. The client's API class unwraps it automatically\. If \`success\` is false, the [error handling](/blog/error-handling-conversational-systems) kicks in\. If \`success\` is true, the \`data\` field contains the typed payload\.

## Backward compatibility requirements

Our rule is simple: existing fields never change type, never get renamed, and never get removed without a version bump. New fields can be added freely (they're additive and don't break old clients that ignore unknown fields). But any change to an existing field is a breaking change.

This means the backend sometimes carries legacy baggage. A field that was named poorly in v1 keeps its poor name in v1 responses. The v2 response can use a better name, but v1 still works. Old clients that haven't updated continue to function.

We track backward compatibility per endpoint. Each endpoint has a documented contract that specifies the response shape for each API version. The test suite includes contract tests that serialize a response and compare it against the documented shape. If the shape changes, the test fails.

The API versioning middleware routes requests to the appropriate handler based on the version. The version comes from a header or path prefix. Old mobile app versions send v1. New ones send v2. The backend serves both simultaneously.

## Breaking change detection in CI

![Supporting diagram](https://pics.nowah.xyz/website-media/infrastructure-057-img-2.webp)

Our continuous integration pipeline catches breaking changes before they reach production. When a developer modifies an API response type, the pipeline compares the new type against the documented contract for each active API version.

If the change is additive (new optional field), the pipeline passes. If the change is breaking (removed field, changed type, renamed field), the pipeline fails with a clear message: "Breaking change detected in Trip response: field departureDate was removed. This breaks API v1 clients."

The developer then has two choices: revert the breaking change and maintain backward compatibility, or bump the API version and create a new handler that serves the new shape while keeping the old handler for existing clients.

This automated detection is the safety net that prevents the Thursday afternoon incident from ever happening again. The human reviewer might miss a breaking change. The CI check never does.

## Minimum version enforcement

Sometimes backward compatibility is not possible. A security vulnerability in the API client. A fundamental change to the authentication flow. A new required field that old clients can't provide.

For these cases, we enforce a minimum app version. The mobile app sends its version with every request. The backend checks the version against a minimum threshold. If the app version is below the minimum, the backend returns a specific error code that the app handles by showing an "Update required" screen.

We use minimum version enforcement sparingly. Forcing an update is disruptive. The traveler might be in the middle of a trip with poor connectivity. They might not be able to update immediately. An "Update required" screen in that moment is a terrible experience.

When we do enforce a minimum version, we give advance notice. The app shows a soft nudge ("A new version is available") for two weeks before the hard enforcement kicks in. This gives most users time to update voluntarily.

## Handling unknown API responses

Even with contracts and versioning, the mobile app must handle unexpected responses gracefully. The backend might return a new field the app doesn't know about. The backend might return a new enum value for a field the app does know about. A network proxy might mangle the response.

Our API client follows a permissive parsing approach: accept what you understand, ignore what you don't\. If the Trip response contains a new \`carbonOffset\` field that the current app version doesn't know about, the parser ignores it\. The trip displays correctly with the fields the app does understand\.

For enum fields, we have a default handling strategy. If the trip status is a value the app doesn't recognize (because the backend added a new status), the app displays a generic state rather than crashing. "Unknown status" is ugly but functional. An app crash is neither.

The critical rule: the app must never crash on an unexpected API response. Partial rendering, graceful fallbacks, and "unknown" states are all acceptable. Crashes are not. A crash on an unexpected response means a single backend change can brick the app for users on old versions.

## Establish contracts between your mobile and backend teams

If you're building a mobile app with a backend API, here's how to prevent the contract drift that causes production incidents.

Define shared types. Even if your mobile and backend use different languages, document the contract in a machine-readable format. a single typed language across the stack interfaces, OpenAPI specs, or Protocol Buffers all work. The point is that the contract is explicit, not implied.

Test backward compatibility automatically. Don't rely on developers to remember which changes are breaking. Write contract tests that fail when the response shape changes. Run them in CI on every pull request.

Version your API from day one. Adding versioning later is painful. Adding it from the start is cheap. Even if you only have v1 for the first year, the versioning infrastructure is ready when you need v2.

Enforce minimum versions as a last resort. Update the minimum version only for security issues or fundamental architectural changes. Every forced update costs user trust.

Build clients that tolerate the unexpected. Unknown fields, unknown enum values, missing optional fields. The client should handle all of these without crashing. Test this explicitly by sending responses with extra fields and verifying the app still works.

---

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](https://app.nowah.xyz).
