Mobile — Mobile App Architecture

API Layer Design for Mobile-First Products

Direct answer

A mobile-first API is designed around screens and journeys, not database tables: aggregated endpoints that let a screen render in one round trip, cursor-based pagination, and contracts that tolerate old clients, because shipped app versions live in the wild for months. On the client, all HTTP concerns — auth, retries, mapping, error taxonomy — belong in one typed API layer that feature code consumes through hooks, never through scattered fetch calls.

The API decisions that hurt mobile products are invisible in a web demo: chatty endpoints feel fine on office Wi-Fi and terrible on a train, and a breaking change is trivial when you can redeploy the client — which a mobile team cannot. This is how I design API layers for products where the phone app is the primary client.

Key facts, with sources

  • React Native's New Architecture became the default in 0.76, the legacy bridge was retired in 0.82, and Hermes V1 shipped as the default JavaScript engine in 0.84. (TO THE NEW Blog)
  • Microsoft retired Visual Studio App Center and CodePush on March 31, 2025, forcing every team that depended on it to migrate their over-the-air update architecture. (microsoft/react-native-code-push GitHub issue)
  • In the State of React Native 2024 survey, Redux drew the most negative feedback at around 18% dissatisfaction, while React's built-in state management (31% positive) and Zustand (21% positive) were the best regarded. (InfoQ)
  • Over 80% of State of React Native 2024 respondents work in teams of up to five developers, meaning most mobile architectures must be maintainable by very small teams. (SSOJet (State of React Native 2024 highlights))
  • Published production examples report Shopify at 86% unified code across its apps and Instagram sharing 85 to 99% of code between iOS and Android. (CatDoes)

Mobile clients break the web's assumptions

Two facts should drive every mobile API decision. First, you cannot redeploy the client: whatever contract you ship, some installed base will still call it many months from now, so every change is a compatibility question. Second, the network is hostile — high latency, sudden drops, expensive radio wakeups — so round trips are the resource to economize, more than payload bytes.

APIs designed database-outward fail both tests: fine-grained resource endpoints force a screen to make several dependent requests, and every response-shape refactor strands old versions. Designing screen-inward flips it — you start from what each screen needs to render and work backward to the contract. That single reorientation drives most of what follows.

Aggregate for the screen, keep writes fine-grained

Reads and writes deserve different shapes. For reads, I want each primary screen to render from one request: a home endpoint that returns the user, active orders, and notifications together, rather than three round trips with the spinner waiting on the slowest. Whether you achieve that with a backend-for-frontend layer, GraphQL, or purpose-built aggregate endpoints matters less than committing to the principle.

Writes go the other way: small, explicit, intention-revealing mutations — submit order, cancel order — rather than a generic update on a large object. Fine-grained writes are easier to make idempotent, easier to authorize, and far easier to retry safely on flaky networks. The asymmetry feels inconsistent to REST purists and is exactly right for the physics of mobile.

Version for an installed base you cannot recall

Plan deprecation before launch, because the alternative is supporting every contract you ever shipped, forever. My baseline: additive changes only within a version — new fields are always safe because clients are built as tolerant readers that ignore unknown fields; breaking changes get a new versioned path; and the app ships with a minimum-supported-version mechanism from day one, so ancient clients can be gently forced to update rather than silently breaking.

Instrument API usage by app version so deprecation is data-driven: you retire an endpoint when the traffic from old clients falls below a threshold you chose, not when someone guesses it is probably fine. The teams that skip the force-update mechanism at launch always regret it — it is trivial to build early and painful to retrofit under incident pressure.

One client-side API layer, typed end to end

On the client, every HTTP concern concentrates in a single layer: base URL and environment selection, auth header injection and token refresh, timeout policy, a retry strategy that only retries idempotent requests, and mapping from wire formats to app types. Feature code never calls fetch directly — it consumes typed hooks, so a contract change is a one-layer fix.

Generate the request and response types from the API schema rather than hand-writing them; hand-written types drift from reality and the drift surfaces as runtime crashes in the field. The hook layer on top pairs each endpoint with its caching policy, which keeps staleness decisions next to the data they govern.

Typed endpoint hook over a single API client
import { useQuery } from '@tanstack/react-query';
import { api } from './client'; // owns baseUrl, auth, retries, mapping

type OrdersResponse = {
  orders: OrderSummary[];
  nextCursor: string | null;
};

export function useOrders(cursor?: string) {
  return useQuery({
    queryKey: ['orders', cursor ?? 'first'],
    queryFn: () => api.get<OrdersResponse>('/v1/orders', { cursor }),
    staleTime: 30_000,
  });
}

Design the error and retry semantics explicitly

Mobile networks guarantee you will retry requests, so the API must make retrying safe. Mutation endpoints accept an idempotency key generated by the client; replaying the same key returns the original result instead of double-charging a card or creating duplicate orders. Cursor-based pagination replaces offsets, because offsets skip or duplicate items when the underlying list changes between requests — which on mobile timescales it always does.

Errors need a taxonomy the client can act on, not just status codes: is this retryable, does it require re-authentication, should the user see a specific message, is the client version unsupported? I put a machine-readable error code in every failure response and map it to behavior in the client's API layer. When error handling lives in one place with one vocabulary, the difference shows up directly in review ratings — users see fewer generic something-went-wrong screens.

When to hire senior help

Architecture is the cheapest place to buy senior expertise, because decisions about state management, navigation, offline strategy, and update infrastructure made in week one determine costs for years. A short engagement with a senior mobile architect before or during MVP planning routinely prevents the rewrite-at-scale scenario that hits teams around their first major growth phase. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Adopting Redux with sagas and heavy boilerplate for a five-screen MVP when built-in React state or Zustand would cover the actual requirements
  • Scattering business logic inside UI components instead of isolating a data layer, making later backend changes or native module swaps expensive
  • Building deployment architecture on a hosted OTA service with no exit plan, a risk the March 2025 CodePush shutdown made concrete for thousands of teams
  • Assuming permanent connectivity and bolting on caching later, instead of designing offline storage and sync conflict resolution before the data layer hardens

Frequently asked questions

Should a mobile-first product use REST or GraphQL?

Both can serve mobile well; the deciding factor is who controls aggregation. GraphQL lets the client compose exactly what a screen needs, which is valuable when screens change faster than the backend team can ship endpoints. Screen-shaped REST endpoints or a backend-for-frontend achieve the same round-trip economy with simpler caching and operations. Choose based on team structure, not fashion.

What is an idempotency key and why do mobile apps need them?

It is a unique client-generated identifier sent with a mutation, letting the server recognize a retry of the same operation and return the original result instead of executing it twice. Mobile apps need them because flaky networks force retries where the client cannot know whether the first attempt succeeded — without keys, retrying a payment can mean charging it twice.

How do I handle old app versions still calling my API?

Assume every shipped version calls you for months. Make changes additive within a version, route breaking changes to new versioned paths, and track traffic by app version so you retire endpoints on data rather than guesses. Ship a minimum-supported-version check in the app from day one, so you can eventually force stragglers to update gracefully.

What state management should a new mobile app use?

For most apps, React's built-in state plus a light library like Zustand is enough; in the State of React Native 2024 survey those two drew the most positive sentiment while Redux drew the most negative at about 18% dissatisfaction. Heavier tooling is justified mainly by large teams, complex shared state, or strict audit requirements.

Do we need offline support from day one?

If users operate in the field, in transit, or in markets with unreliable networks, yes, because retrofitting offline-first sync onto an online-only data layer is one of the most expensive refactors in mobile. If the app is unusable without live data anyway, graceful error and retry handling may be sufficient.

What are over-the-air updates and should our app use them?

OTA updates push JavaScript-level fixes directly to users without waiting for app store review, which is valuable for hotfixes. Microsoft's CodePush was retired on March 31, 2025, so current options are EAS Updates, a self-hosted CodePush server, or third-party services, and updates must stay within store policies that prohibit changing an app's core purpose.

Bottom line: Dhairya Senjaliya ships Mobile — Mobile App Architecture projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership