Python — Backend APIs

Backend API Design for React Native Apps

Direct answer

A backend API for a React Native app should be designed around screens, not database tables: one endpoint per screen or intent, a single consistent response envelope, cursor pagination, and machine-readable error codes the app can map to UI states. Because shipped app versions live for months, every change must be additive, and token refresh, offline retries, and slow networks need to be first-class design inputs rather than afterthoughts.

Most APIs that frustrate mobile teams were designed as generic data services and retrofitted for the app. Having built both sides — the React Native client and the FastAPI backend — this is what I design differently when the primary consumer is a phone on a bad network.

Key facts, with sources

  • Postman's 2025 State of the API report, based on more than 5,700 developers and API professionals, found 83.2% of respondents adopting some level of an API-first approach. (Postman State of the API 2025)
  • The same Postman 2025 research found 65% of organizations now generate revenue directly from their API programs. (Postman State of the API 2025)
  • One in four developers (24%) now design APIs specifically for consumption by AI agents, while 89% use generative AI tools in their daily work. (Business Wire)
  • APIs make up 57% of the dynamic (non-cacheable) internet traffic processed by Cloudflare, and that share continues to grow. (Cloudflare)
  • Salt Security's 2024 State of API Security report found 95% of respondents experienced API security problems in production, with security incidents more than doubling year over year from 17% to 37% of organizations. (Salt Security)

Design endpoints around screens, not tables

A phone on cellular pays dearly for every round trip, so the worst mobile API is a faithful mirror of your database: fetch the user, then their subscriptions, then each subscription's plan, four requests deep before the first render. I design the contract by walking the app's screens: each major screen or user intent gets one endpoint that returns everything needed for first paint.

This feels denormalized and repetitive from a backend purist's view, and that is fine — the backend's job is to make the client simple, not to expose a tidy schema. Aggregation, joining, and shaping are cheap on the server and expensive on the device. When a screen changes, its endpoint changes with it, which keeps the coupling honest and visible.

One envelope, machine-readable errors

React Native apps handle every response through shared client code, so inconsistency multiplies: if some endpoints return bare arrays, others wrap in data, and errors are sometimes strings and sometimes objects, the client accumulates defensive parsing for each shape. I standardize a single envelope with a data slot, a structured error, and server time — the last one because client clocks are unreliable and countdowns or token expiry math should never trust the device.

Error codes must be machine-readable constants, not prose. The app needs to distinguish an expired token (silent refresh), a validation failure (highlight a field), and a server fault (retry banner) programmatically. Human-readable messages are for display; codes drive behavior.

Consistent response envelope with Pydantic
from datetime import datetime
from typing import Generic, TypeVar

from pydantic import BaseModel

T = TypeVar("T")


class ApiError(BaseModel):
    code: str  # machine-readable, e.g. "AUTH_TOKEN_EXPIRED"
    message: str  # safe to show the user
    field: str | None = None  # set for validation errors


class ApiResponse(BaseModel, Generic[T]):
    data: T | None = None
    error: ApiError | None = None
    server_time: datetime


class HomeFeed(BaseModel):
    items: list[FeedItem]
    unread_count: int
    next_cursor: str | None


# endpoint returns ApiResponse[HomeFeed]

Auth designed for token refresh and app lifecycles

Mobile sessions are long-lived — users expect to stay signed in for months — so the standard design is a short-lived access token paired with a long-lived refresh token, rotated on use. The API's job is to make refresh cheap and unambiguous: a dedicated refresh endpoint, and a distinct error code for expired-token versus invalid-token, because the client must silently refresh on the first and hard-logout on the second.

Design for the stampede case too: when an app wakes from background, several queued requests may fire with an expired token simultaneously. The client should single-flight the refresh, but the backend must also tolerate a brief overlap window where the previous refresh token is retried, or a race will log users out randomly — one of the most common mobile auth bugs I get called in to fix.

Payloads, pagination, and slow networks

Assume the worst network your users actually have, not your office WiFi. Keep payloads lean: no fields the screen does not render, enable gzip or brotli compression at the edge, and return image URLs with size variants so the app never downloads a full-resolution photo for a thumbnail row.

Lists should use cursor pagination, which stays consistent while new items are inserted — offset pagination visibly duplicates or skips rows during pull-to-refresh. Support conditional requests with ETags on cacheable resources so a refresh of unchanged data costs a 304 instead of a full payload. And set explicit timeouts expectations: an endpoint that occasionally takes tens of seconds will look like an outage to a mobile client that gave up long before.

Evolving the API under app-store lag

The defining constraint of mobile backends: you cannot deploy the client. After a release, app-store review takes days and users update on their own schedule, so every historical app version you ever shipped is a live consumer. The contract therefore evolves additively only — new fields and endpoints are safe, removals and renames are not, and semantic changes to existing fields are the most dangerous because nothing crashes, data just goes quietly wrong.

Two mechanisms make this survivable. Send the app version on every request and log it, so you can see exactly which builds still depend on old behavior. And build a server-driven minimum-version gate into the app from the first release — a config endpoint the app checks on launch — so that when you truly must break something, you can force an upgrade screen instead of shipping bugs to stragglers.

When to hire senior help

Bring in senior backend help when you are defining the public contract of your API (auth model, versioning, rate limits), because those decisions are nearly impossible to change once partners integrate. It is also warranted when incidents like timeout cascades, N+1 query storms, or authorization bugs start appearing, since these are pattern problems a senior engineer has usually fixed many times before. 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 Python — Backend APIs projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Shipping list endpoints without pagination or rate limiting, then having one integration partner's bulk pull take down the database
  • Launching with no versioning strategy, so the first breaking schema change strands mobile apps that cannot be force-updated
  • Missing per-object authorization checks (broken object-level authorization), letting any authenticated user read other tenants' records by iterating IDs
  • Treating internal APIs as trusted and undocumented, then exposing them to partners or frontends later without adding auth, quotas, or contracts

Frequently asked questions

What makes a good backend API for a React Native app?

One endpoint per screen so first paint needs a single round trip, a consistent response envelope with machine-readable error codes, cursor-based pagination, short-lived access tokens with refresh rotation, and strictly additive changes because old app versions stay live for months. The backend should absorb aggregation and formatting work so the client stays thin and the app feels fast on poor networks.

Should the mobile app call multiple endpoints or one aggregated endpoint per screen?

One aggregated endpoint per screen is usually right. Each extra request on a cellular connection adds latency, failure surface, and battery cost, and coordinating multiple in-flight requests complicates loading states. Server-side aggregation is cheap by comparison. Split requests only when parts of a screen have genuinely different lifetimes, such as a static profile plus a live-updating feed.

How do I change an API without breaking old versions of a mobile app?

Make only additive changes: add fields and endpoints, never remove or rename existing ones, and never change what an existing field means. Log the app version on every request so you know which builds still use old behavior, and ship a server-controlled minimum-version check in the app early so you can force an upgrade screen when a breaking change is truly unavoidable.

Is Python fast enough for our backend API?

For the vast majority of products, yes: async Python frameworks handle thousands of requests per second per instance, and real-world latency is usually dominated by database queries and network calls, not language speed. Teams typically only outgrow Python at extreme throughput, and even then usually rewrite specific hot services rather than the whole backend.

How much API security do we need at MVP stage?

At minimum: authentication on every endpoint, per-object authorization checks, rate limiting, and input validation. Salt Security found 95% of organizations hit API security problems in production and incidents doubled year over year, so retrofitting security after a breach is far costlier than building these four basics in from day one.

REST or GraphQL for a new product?

REST with an OpenAPI spec remains the default for most backends because tooling, caching, and hiring are simpler. GraphQL earns its complexity when many differently shaped clients consume the same data graph. Starting with REST and adding GraphQL later where needed is a common, low-risk path.

Bottom line: Dhairya Senjaliya ships Python — Backend APIs 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