Backend Architecture for Mobile and Web Products
Architecting backends for unified mobile and web clients requires balancing cellular radio states, process lifecycles, release cadences, BFFs, and offline sync.

Backend Architecture for Mobile and Web Products
Engineering teams frequently start with a deceptive assumption: if both mobile applications and web frontends consume the same underlying business domain, they should consume the exact same backend API.
In early-stage prototypes, this assumption appears efficient. A single set of REST or GraphQL endpoints powers the desktop browser dashboard, the iOS app, and the Android client. Business logic is centralized, database queries are written once, and initial delivery accelerates.
As production scale increases, this unified facade breaks down. Desktop web browsers and native mobile applications do not merely differ in display size; they operate under fundamentally divergent physical, operational, and lifecycle constraints:
- Network physics: Desktop web clients typically operate over stable Wi-Fi or wired broadband connections with predictable latency and constant power. Mobile clients operate over cellular radios managed by state machines that cycle power and experience packet loss, frequent IP address reassignment, and latency jitter during cell tower handovers.
- Release cadence and immutability: Web code can be deployed, tested, and rolled back atomically in seconds. Mobile apps are compiled native binaries subject to app store review friction and perpetual multi-version client fragmentation, requiring backends to support years-old client builds concurrently.
- Execution and memory models: Browser tabs run with gigabyte-scale memory heaps and virtual memory backing on machines connected to continuous power. Mobile operating systems aggressively hibernate processes and terminate apps via low-memory killers when deserialization spikes physical RAM usage.
- Synchronization and push channels: Web browsers maintain persistent WebSockets or Server-Sent Events (SSE) while open. Mobile operating systems sever background TCP connections to preserve battery, routing updates through platform push notification daemons and opportunistic background fetch workers.
Designing a resilient backend architecture requires rejecting the "one-size-fits-all" API anti-pattern. This guide explores the architectural patterns, network topologies, and synchronization protocols required to serve mobile and web clients from a unified domain core while respecting their divergent runtime realities.
The Divergence Matrix: Mobile vs. Web Constraints
Before choosing an API topology or serialization format, architects must understand how the physical constraints of mobile devices diverge from web clients.
| Architectural Dimension | Modern Web Frontend | Native Mobile (iOS / Android) |
| :--- | :--- | :--- |
| Primary Network Physics | Fixed broadband / Wi-Fi; stable RTT; minimal radio transition overhead. | Cellular (LTE / 5G NR); variable latency; carrier-managed Radio Resource Control (RRC) power states. |
| Binary Deployment | Continuous deployment; instant bundle rollouts; atomic version lockstep. | Compiled binaries; app store review queues; indefinite client version fragmentation across users. |
| Process Lifecycle | Long-lived browser tab processes; virtual memory swap; background tab throttling. | Aggressive OS hibernation; zero-warning termination by OS memory daemons (jetsam / lmkd). |
| Payload Sensitivity | High bandwidth tolerance; fast V8/JavaScriptCore JIT deserialization on desktop CPUs. | Strict bandwidth/battery constraints; JSON parsing memory amplification can trigger OOM kills. |
| Background Sync | WebSockets, Server-Sent Events (SSE), and Web Workers active during user session. | Push gateways (APNs / FCM); OS-scheduled background tasks (BGAppRefreshTask / WorkManager). |
| Local Storage | IndexedDB, Cache API, LocalStorage (subject to browser eviction policies). | Embedded ACID databases (SQLite, Room, Core Data, WatermelonDB) with persistent local disk storage. |
Cellular Radio Resource Control (RRC) and Network Physics
A common backend engineering mistake is treating mobile cellular connections as slow Wi-Fi. The defining characteristic of cellular data transmission is not merely throughput; it is the Radio Resource Control (RRC) protocol implemented in the device baseband processor and telecommunications radio access network (eNodeB in 4G LTE, gNodeB in 5G NR).
To balance radio availability against high battery consumption, the cellular modem operates across discrete RRC states:
- RRC_IDLE: The radio receiver is powered down into a low-power paging cycle. No network traffic can be transmitted until a random-access channel (RACH) handshake establishes a radio bearer. Transitioning from IDLE to an active state introduces a latency penalty (often 50ms to 200ms depending on carrier configuration and signal quality).
- RRC_CONNECTED (Active/DCH): The radio transmits and receives at maximum throughput, consuming substantial power from the device battery.
- Tail State (Inactivity Timer / RRC_INACTIVE): When network transmission ceases, the carrier baseband timer does not drop immediately to IDLE. Instead, it holds the radio in an intermediate state for several seconds (the tail state) anticipating subsequent packets. If no packets arrive before the timer expires, it drops back to IDLE.
[RRC_IDLE] (Low Power)
│
│ Outgoing Packet / Channel Request (50-200ms Latency Spike)
▼
[RRC_CONNECTED] (High Power Draw, Active Transmission)
│
│ Inactivity Timer Triggered (Transmission Finishes)
▼
[TAIL STATE / INACTIVE] (Intermediate Power Draw, Waiting for Bursts)
│
│ Carrier Inactivity Timeout Expires
▼
[RRC_IDLE] (Radio Powers Down)
The Architectural Implication: Chatter vs. Batching
On fixed broadband or Wi-Fi, making five sequential HTTP calls to fetch a user profile, notification count, cart items, active orders, and personalized banners incurs standard network transit times.
On cellular networks, if those five calls are spaced out (e.g., periodic polling or uncoordinated frontend component mounting), they repeatedly trigger the RRC state machine, locking the cellular modem into continuous high-power tail states. This behavior rapidly depletes device battery and multiplies perceived latency.
Architectural Rule: Mobile backends must prioritize request coalescing and coarse-grained aggregation. Payloads must be aggregated server-side over low-latency datacenter backbones so that the mobile client can wake its radio once, receive an aggregated bundle, and allow the baseband processor to quickly return to a low-power idle state.
Native Binary Immutability and Multi-Version Client Fragmentation
When a web application introduces a breaking schema change—such as renaming a field from customer_name to account_name—the deployment pipeline can atomically deploy the backend service and the web frontend bundle simultaneously. If an end-user hits the web app, their browser downloads the latest JavaScript bundle.
Native mobile applications offer zero deployment atomicity:
- Review bottlenecks: New application releases must clear Apple App Store and Google Play reviews, introducing delays ranging from hours to several days.
- Delayed user adoption: Users frequently disable automatic app updates, maintain metered cellular download limits, or use legacy operating systems incompatible with newer app releases.
- Immutability: Once an application binary is compiled and installed on a user's phone, its deserialization logic, networking code, and schema parsing cannot be modified retroactively.
Backend Production Cluster (v3.2)
│
├── Web Frontend (v3.2) ──────> Deployed concurrently with backend
├── Mobile App (v3.2) ────────> 45% of active mobile user base
├── Mobile App (v2.8) ────────> 35% of active mobile user base
└── Mobile App (v1.4) ────────> 20% of active mobile user base (deployed 14 months ago)
If the backend drops a deprecated field, renames an enum value, or alters a JSON data type (e.g., changing an integer ID to a UUID string), older mobile binaries will crash on launch due to deserialization exceptions (such as DecodingError in Swift or JsonParseException in Kotlin).
Architectural Rule: Apply Postel's Law ("Be conservative in what you send, liberal in what you accept") and adopt the Tolerant Reader Pattern:
- Additive changes only: Never remove fields, rename properties, or change value types in shared endpoints. Introduce new optional fields alongside existing fields.
- Explicit deprecation horizons: Track incoming client versions via custom telemetry headers (e.g.,
X-Client-App-Version: 2.4.1) and maintain backward-compatible field mappings in the gateway or BFF until analytics confirm adoption falls below acceptable thresholds.
Execution Models: Process Lifecycles and Memory Budgets
Desktop web applications run in modern browser engines (V8, JavaScriptCore, Gecko) backed by desktop operating systems with large physical RAM budgets and virtual memory paging. A web dashboard rendering 500 rows with nested customer metadata rarely risks an operating system crash.
Mobile operating systems enforce strict, unforgiving memory boundaries:
- iOS Jetsam Daemon: iOS does not use traditional disk swap for active user processes. Instead, the
jetsamsystem daemon continuously monitors system-wide memory pressure. Each device class has an unalterable maximum footprint limit. If an app exceeds this threshold—or spikes memory consumption while transitioning to the background—jetsamterminates the process immediately with an uncatchableEXC_RESOURCEor0x8badf00dexception. - Android Low Memory Killer Daemon (
lmkd): Android assigns each process anoom_score_adjscore based on whether the app is in the foreground, visible, or cached in the background. Under low-memory conditions, the kernel terminates high-scoring processes instantly.
The JSON Deserialization Memory Multiplier
A backend returning a 10MB raw JSON payload might seem reasonable over modern 5G cellular speeds. However, parsing a 10MB JSON string into native object graphs requires allocating memory for string buffers, dictionary hash tables, native model instances, and garbage collection overhead.
In resource-constrained mobile runtimes, deserializing a 10MB JSON payload can cause a transient memory allocation spike of 30MB to 50MB. On an older mobile device or an app already operating near its jetsam allocation boundary, this allocation spike triggers an instant process termination.
Architectural Rule: Implement strict server-side response pruning and cursor-based pagination. Mobile endpoints must never return raw database entities with dozens of unused properties.
Background Synchronization vs. Push Gateways
Web frontends can maintain persistent full-duplex TCP connections (such as WebSockets) for real-time dashboards because desktop machines remain powered and active.
Mobile operating systems immediately suspend background app processes after a brief grace period (typically seconds) to conserve power and CPU cycles. Any persistent socket connection established by the mobile app is severed by the kernel.
To synchronize data when the app is backgrounded or closed, backends must integrate with platform notification infrastructures:
- Apple Push Notification service (APNs): Provides high-priority user-facing alerts and background data-only pushes (
content-available: 1). The OS wakes the app in the background for a strict execution window (typically under 30 seconds) to fetch updated state. - Firebase Cloud Messaging (FCM): Manages push routing for Android devices, distinguishing between high-priority display messages and data payloads that invoke Android background jobs.
- Opportunistic Background Workers: Client frameworks (iOS
BGAppRefreshTask, AndroidWorkManager) notify the backend during OS-determined maintenance windows (e.g., when the device is connected to unmetered Wi-Fi and wall power).
API Topologies: One-Size-Fits-All vs. BFF vs. GraphQL vs. gRPC
When structuring backend services to support both web and mobile, architecture teams evaluate four primary topologies:
Topology A: One-Size-Fits-All Monolithic API
[Web App] ────┐
├───> [Shared Domain REST API] ───> [Databases]
[Mobile App] ─┘
Topology B: Backend for Frontend (BFF) Pattern
[Web App] ────────> [Web BFF] ───────┐
├───> [Domain Microservices]
[Mobile App] ─────> [Mobile BFF] ────┘
Topology C: GraphQL Federation
[Web App] ────┐
├───> [Federated GraphQL Gateway] ───> [Subgraphs / Services]
[Mobile App] ─┘
Topology D: gRPC and gRPC-Web Gateway
[Web App] ────────> [Envoy gRPC-Web Proxy] ─┐
├───> [gRPC Services]
[Mobile App] ─────> [Direct gRPC (HTTP/2)] ──┘
1. The One-Size-Fits-All API Anti-Pattern
In this design, a single set of CRUD endpoints serves every consumer. A /api/v1/orders/{id} endpoint returns every column associated with an order, its line items, customer audit records, warehouse routing codes, and tax calculations.
- Why it fails on mobile:
- Massive over-fetching: Mobile screens display only a fraction of the data required by desktop dashboards, but the mobile client must still download, parse, and allocate memory for hundreds of unused fields.
- Severe under-fetching and chatter: When a mobile screen requires data from multiple domain boundaries (e.g., active orders, customer balance, and recommended items), it must make multiple sequential HTTP calls.
- Coupled release cycles: Any modification to satisfy web requirements risks breaking legacy mobile clients.
2. The Backend for Frontend (BFF) Pattern
Formulated by Sam Newman, the Backend for Frontend (BFF) pattern introduces dedicated API layers tailored specifically to the needs of distinct client archetypes.
Instead of one generic API, the architecture maintains two entry points:
- Web BFF: Handles server-side rendering (SSR), high-volume tabular datasets, multi-tenant administrative filtering, and session cookie authentication.
- Mobile BFF: Handles coarse-grained endpoint aggregation, field pruning, device-specific image formatting, client version compatibility mappings, and token-based authentication.
Key Architectural Responsibilities of a Mobile BFF:
- Request Aggregation: Coordinates multiple internal microservices in parallel over internal high-bandwidth networks, assembling a single response object for the mobile screen.
- Payload Trimming: Strips administrative fields, internal IDs, and audit records, delivering the minimal JSON structure required for the active screen.
- Dynamic Asset Adaptation: Reads client headers (
X-Device-DPR,X-Screen-Width) and formats media URLs with targeted compression, dimensions, and modern formats (e.g., WebP or AVIF). - Graceful Failure Degradation: Isolates failures in non-critical downstream dependencies (e.g., personalized recommendations) so that core domain data (e.g., user profile and orders) is still delivered.
3. GraphQL Federation
GraphQL allows clients to request exact fields declaratively, resolving over-fetching and under-fetching natively. With Apollo Federation or GraphQL Mesh, individual domain teams expose subgraphs that are stitched into a unified gateway.
- Strengths: Eliminates payload over-fetching; enables client engineers to iterate on UI requirements without backend endpoint changes.
- Production Trade-offs:
- HTTP Caching Friction: Most GraphQL queries use HTTP
POSTrequests, bypassing standard HTTP reverse proxy caches (Varnish, Cloudflare, Akamai) that rely onGETURLs and cache-control headers. - Resolver N+1 Multiplications: Complex nested queries can inadvertently trigger hundreds of internal database queries unless batched with DataLoader mechanisms.
- Client Footprint: GraphQL client libraries (Apollo Client, Relay) add substantial binary weight and runtime memory overhead compared to lightweight native HTTP clients.
- HTTP Caching Friction: Most GraphQL queries use HTTP
4. gRPC and gRPC-Web
gRPC utilizes Protocol Buffers (Protobuf) for compact binary serialization and runs over HTTP/2 multiplexed streams.
- Strengths: Strict compile-time contract typing; binary wire format reduces bandwidth consumption by 30% to 60% compared to equivalent JSON payloads; zero ambiguity in field types.
- Production Trade-offs:
- Browser Restrictions: Standard web browsers do not expose fine-grained control over HTTP/2 framing required by native gRPC. Web frontends must route through a translation layer (such as an Envoy reverse proxy) converting gRPC-Web HTTP/1.1 or standard HTTP/2 calls into gRPC.
- Native Mobile Support: iOS and Android run native gRPC libraries smoothly, making it exceptional for high-throughput mobile-to-backend communication, though network debugging requires Protobuf decoders.
Topology Comparison Matrix
| Metric / Requirement | One-Size-Fits-All API | Backend for Frontend (BFF) | Federated GraphQL | gRPC / gRPC-Web |
| :--- | :--- | :--- | :--- | :--- |
| Payload Precision | Poor (heavy over-fetching) | Excellent (custom tailored) | Optimal (declarative fields) | High (compact binary) |
| Network Chatter | High (multiple roundtrips) | Minimal (server-aggregated) | Minimal (single query) | Moderate to Minimal |
| HTTP Edge Caching | Standard GET caching | Standard GET caching | Difficult (POST by default) | Requires specialized gateway |
| Deployment Independence | High risk of client breakage | Decoupled per frontend team | Shared schema governance | Contract-driven via Protobuf |
| Client Runtime Cost | Minimal (native fetch) | Minimal (native fetch) | High (GraphQL client engine) | Moderate (Protobuf runtime) |
| Recommended Context | Small, single-client prototypes | Multi-client production platforms | Diverse, rapidly changing UIs | Microservice backbones / high-throughput apps |
Production Implementation: Resilient Mobile BFF Controller
The following production-ready TypeScript implementation illustrates a Mobile BFF Aggregation Controller.
It demonstrates:
- Parallel Downstream Coordination: Utilizing
Promise.allSettledto query downstream microservices simultaneously over low-latency internal networks. - Graceful Degradation: If non-critical downstream services (e.g., Personalized Recommendations) fail or time out, the controller still serves core domain data (User Profile and Active Orders).
- Payload Pruning: Internal database primary keys, tenant IDs, and audit metadata are stripped prior to wire serialization.
- Device-Specific Image Optimization: The controller inspects device pixel ratio (DPR) and screen width headers to return image URLs optimized for the physical screen.
- Timeout Protection: Enforcing strict timeouts using
AbortControllerto prevent downstream latency spikes from exhausting backend worker pools.
// types/bff.ts
export interface MobileClientHeaders {
userId: string;
deviceDpr: number;
viewportWidth: number;
clientVersion: string;
}
export interface DownstreamUserProfile {
id: string;
tenantId: string;
displayName: string;
email: string;
avatarOriginalUrl: string;
internalAuditFlags: string[];
lastPasswordChange: string;
}
export interface DownstreamOrder {
id: string;
customerId: string;
status: "pending" | "processing" | "shipped" | "delivered";
totalCents: number;
itemCount: number;
warehouseRoutingCode: string;
paymentGatewayRef: string;
createdAt: string;
}
export interface DownstreamRecommendation {
productId: string;
name: string;
priceCents: number;
thumbnailUrl: string;
relevanceScore: number;
algorithmId: string;
}
export interface MobileDashboardResponse {
user: {
displayName: string;
avatarUrl: string;
};
activeOrders: Array<{
id: string;
status: DownstreamOrder["status"];
totalCents: number;
itemCount: number;
}>;
recommendations: Array<{
id: string;
name: string;
priceCents: number;
imageUrl: string;
}>;
meta: {
degraded: boolean;
serverTimeUtc: string;
};
}
// controllers/mobile-dashboard.controller.ts
import {
MobileClientHeaders,
DownstreamUserProfile,
DownstreamOrder,
DownstreamRecommendation,
MobileDashboardResponse,
} from "./types/bff";
export class MobileDashboardController {
private readonly userServiceBase = "http://internal-user-service.local";
private readonly orderServiceBase = "http://internal-order-service.local";
private readonly recServiceBase = "http://internal-recommendation-service.local";
/**
* Generates a device-optimized image URL based on target logical width and client DPR.
*/
private buildOptimizedImageUrl(rawUrl: string, logicalWidth: number, dpr: number): string {
// Clamp DPR between 1.0 and 3.0 to avoid excessive memory allocation on high-density displays
const safeDpr = Math.min(Math.max(dpr, 1.0), 3.0);
const targetPhysicalPixels = Math.round(logicalWidth * safeDpr);
return `${rawUrl}?w=${targetPhysicalPixels}&format=webp&q=80`;
}
/**
* Aggregates and prunes dashboard data for mobile clients.
*/
public async getDashboard(
headers: MobileClientHeaders,
parentSignal?: AbortSignal
): Promise<MobileDashboardResponse> {
const { userId, deviceDpr, viewportWidth } = headers;
// Timeout: Enforce 800ms limit on internal aggregation to protect mobile UX
const timeoutController = new AbortController();
const timeoutId = setTimeout(() => timeoutController.abort(), 800);
// Link parent cancellation (if client disconnected) with internal timeout
const effectiveSignal = parentSignal
? AbortSignal.any([parentSignal, timeoutController.signal])
: timeoutController.signal;
try {
// 1. Initiate parallel downstream requests
const userPromise = fetch(`${this.userServiceBase}/api/v1/users/${userId}`, {
headers: { Accept: "application/json" },
signal: effectiveSignal,
}).then(async (res) => {
if (!res.ok) throw new Error(`User service error: ${res.status}`);
return (await res.json()) as DownstreamUserProfile;
});
const ordersPromise = fetch(`${this.orderServiceBase}/api/v1/users/${userId}/orders/active`, {
headers: { Accept: "application/json" },
signal: effectiveSignal,
}).then(async (res) => {
if (!res.ok) throw new Error(`Order service error: ${res.status}`);
return (await res.json()) as DownstreamOrder[];
});
const recsPromise = fetch(
`${this.recServiceBase}/api/v1/recommendations?userId=${userId}&limit=4`,
{
headers: { Accept: "application/json" },
signal: effectiveSignal,
}
).then(async (res) => {
if (!res.ok) throw new Error(`Rec service error: ${res.status}`);
return (await res.json()) as DownstreamRecommendation[];
});
// 2. Coordinate responses with Promise.allSettled
const [userResult, ordersResult, recsResult] = await Promise.allSettled([
userPromise,
ordersPromise,
recsPromise,
]);
// 3. Evaluate Critical Domain Boundaries
// If User Profile or Orders fail, the dashboard cannot provide meaningful utility
if (userResult.status === "rejected") {
throw new Error(`Critical dependency failed (User): ${userResult.reason.message}`);
}
if (ordersResult.status === "rejected") {
throw new Error(`Critical dependency failed (Orders): ${ordersResult.reason.message}`);
}
const rawUser = userResult.value;
const rawOrders = ordersResult.value;
// 4. Graceful Degradation for Non-Critical Dependencies
// If recommendations fail or time out, return an empty array without failing the request
let recommendations: MobileDashboardResponse["recommendations"] = [];
let isDegraded = false;
if (recsResult.status === "fulfilled") {
const itemWidth = Math.round(viewportWidth / 2); // 2-column mobile grid
recommendations = recsResult.value.map((rec) => ({
id: rec.productId,
name: rec.name,
priceCents: rec.priceCents,
imageUrl: this.buildOptimizedImageUrl(rec.thumbnailUrl, itemWidth, deviceDpr),
}));
} else {
isDegraded = true;
}
// 5. Server-Side Payload Pruning: Return only fields strictly necessary for the screen
return {
user: {
displayName: rawUser.displayName,
avatarUrl: this.buildOptimizedImageUrl(rawUser.avatarOriginalUrl, 64, deviceDpr),
},
activeOrders: rawOrders.map((order) => ({
id: order.id,
status: order.status,
totalCents: order.totalCents,
itemCount: order.itemCount,
})),
recommendations,
meta: {
degraded: isDegraded,
serverTimeUtc: new Date().toISOString(),
},
};
} finally {
clearTimeout(timeoutId);
}
}
}
Server-Driven UI (SDUI): Architecture, Schema, and Safety
As applications expand, updating native mobile layouts via regular app store releases becomes a major bottleneck for marketing promotions, personalized home feeds, and dynamic checkout flows.
Server-Driven UI (SDUI) solves this by shifting component layout hierarchy and configuration from hardcoded client views to backend JSON payloads.
Backend SDUI Endpoint ───> JSON Component Tree ───> Mobile Dynamic Component Renderer
├── HeaderWidget (v1)
├── CarouselWidget (v2)
└── UnknownWidget ──> [Graceful Fallback]
The Component Contract
In SDUI, the backend does not transmit raw HTML. Instead, it emits typed, platform-agnostic layout nodes:
{
"screen": "home_feed",
"version": "2.1.0",
"components": [
{
"type": "hero_banner",
"id": "banner_spring_sale",
"version": 1,
"data": {
"title": "Spring Product Updates",
"subtitle": "Explore the new architecture tools",
"actionUrl": "openstair://products/spring-catalog"
}
},
{
"type": "horizontal_carousel",
"id": "carousel_recommended",
"version": 2,
"data": {
"header": "Recommended for You",
"items": [
{ "id": "p_101", "label": "API Gateway", "badge": "New" },
{ "id": "p_102", "label": "Sync Engine", "badge": "Popular" }
]
}
}
]
}
The Golden Rules of Production SDUI
- Strict Unknown-Component Fallback:
Native mobile code must never throw an unhandled deserialization exception when encountering an unrecognized
type. If an older v1.2 app receives atype: "ar_product_preview"introduced in v2.0, the client parser must ignore the component and collapse the layout spacing gracefully. - Decouple Data Fetching from Layout Definition: Avoid turning SDUI into an unbounded nested query engine. The SDUI payload should define the visual structure and lightweight display data, with heavy dynamic lists paginated via standard API contracts.
- Payload Caching and Schema Validation: Validate every emitted component against strict JSON Schema definitions during CI/CD to prevent malformed properties from reaching production clients.
Offline-First Architecture and Data Synchronization
Mobile applications operate in intermittent connectivity environments: subway transit, elevators, rural cell coverage, and airplane mode. Modern users expect mobile apps to remain responsive offline, queuing edits locally and synchronizing seamlessly when connectivity resumes.
Building an offline-first architecture requires three core layers:
[Mobile UI]
│
▼ (Optimistic Updates)
[Local Embedded Storage] (SQLite / Room / Core Data)
│
▼ (Pending Mutations)
[Local Mutation Outbox]
│
▼ (Synchronize Over Network)
[Backend Sync Controller] ───> [Conflict Resolution Engine] ───> [Authoritative Database]
1. Local Persistence and the Mutation Outbox
To provide instant UI feedback, the mobile application applies state modifications to local storage (such as SQLite, Room, Core Data, or WatermelonDB) optimistically.
Simultaneously, the client records the mutation in a persistent Mutation Outbox:
{
"mutationId": "b1a7d6e4-4c28-4e89-8d19-9a2c1f56a310",
"operation": "UPDATE_TASK_TITLE",
"entityId": "task_882",
"payload": { "title": "Finalize Mobile BFF Spec" },
"clientTimestamp": "2025-03-05T10:14:00Z",
"baseRevision": 14,
"syncStatus": "PENDING"
}
When network connectivity is re-established, the background synchronization engine transmits outbox entries to the backend sequentially.
2. Synchronization Protocols: Delta Sync vs. Full Snapshot
Never force mobile clients to download full collection snapshots upon reconnecting. Backends must implement Delta Sync using monotonic sequence markers:
- Monotonic Revision Watermarks: The backend database maintains a monotonically increasing sequence ID or update timestamp per entity.
- Cursor Sync Request: The client requests updates by providing its highest known watermark:
GET /api/v1/sync/tasks?since_revision=14 - Tombstones for Deletions: In an offline-first system, records must never be hard-deleted from the database immediately. The backend writes a tombstone (
deleted_at: "2025-03-05T10:15:00Z",is_deleted: true) so that syncing clients know to remove the record from their local SQLite stores.
3. Conflict Resolution Strategies
When multiple clients (e.g., a web dashboard and a disconnected mobile phone) modify the same entity concurrently, the backend must resolve state divergence.
A. Last-Write-Wins (LWW)
In Last-Write-Wins, the backend compares timestamps and accepts the update with the latest time.
- Critical Vulnerability: Mobile device clocks are notoriously unreliable. Clock drift, incorrect user timezone configuration, or manual clock adjustments mean client-generated timestamps cannot be trusted for authoritative sequencing. If LWW is used, timestamps must be assigned by the authoritative server clock upon receipt, or updates must be ordered by Lamport logical clocks.
B. Conflict-Free Replicated Data Types (CRDTs)
CRDTs are mathematically designed data structures that converge to the identical state regardless of network message reordering or concurrent updates:
- State-based CRDTs (CvRDT): Clients and servers exchange complete internal state representations, which are merged via an idempotent mathematical join function (e.g., observed-remove sets or state-based register maps).
- Operation-based CRDTs (CmRDT): Clients transmit commutative mutation operations that can be applied in any arrival order.
- Trade-off: CRDTs provide mathematical consistency without central coordination locks, but they introduce data storage overhead (tombstones, actor IDs, version vectors) and architectural complexity.
C. Domain-Specific Operational Merging
For transactional business workflows (e.g., e-commerce inventory, banking balances, or workflow approvals), conflict resolution should never rely on arbitrary field replacement:
- Use Event Sourcing or append-only ledger entries. Instead of
SET balance = 100, writeDEBIT 20orCREDIT 50. - Sequential operations can then be evaluated against business invariants on the server, returning a deterministic rejection or acceptance code.
Production Verification Checklist
Before deploying a unified backend serving both mobile and web clients, verify that your architecture satisfies the following operational criteria:
Network and Radio Efficiency
- [ ] Coarse-Grained Aggregation: Mobile endpoints aggregate data across domain boundaries into single-roundtrip responses to minimize cellular RRC state transitions.
- [ ] Server-Side Payload Pruning: Unused database columns, tenant identifiers, and internal audit flags are excluded from mobile payloads.
- [ ] Dynamic DPR Asset Adaptation: Image URLs are parameterized to deliver exact pixel dimensions and modern formats (WebP/AVIF) matched to client
X-Device-DPRand screen width headers. - [ ] Gzip / Brotli Compression: Dynamic compression is enabled on the gateway or BFF for all JSON payloads exceeding 1KB.
Versioning and Backward Compatibility
- [ ] Tolerant Reader Enforcement: All JSON deserialization models on mobile ignore unrecognized fields without throwing exceptions.
- [ ] Strict Additive Schema Evolution: Properties are never renamed or removed from existing API contracts without a documented deprecation horizon.
- [ ] Client Version Tracking: Gateway logs and traces record incoming
X-Client-App-VersionandX-Platformheaders to monitor legacy version usage. - [ ] SDUI Unknown Fallbacks: Dynamic UI parsers gracefully skip unrecognized component types without crashing or distorting layout spacing.
Reliability and Resilience
- [ ] Isolated Parallel Execution: Aggregation layers use
Promise.allSettledor equivalent concurrency primitives with individual timeouts. - [ ] Graceful Degradation Paths: Non-critical dependency failures (recommendations, marketing banners) degrade gracefully while core domain data succeeds.
- [ ] Idempotency Keys on Write Endpoints: All state-mutating requests (
POST,PUT,PATCH) support uniqueIdempotency-Keyheaders to safely handle mobile network retries. - [ ] Cursor-Based Pagination: Endpoints with growing datasets utilize stable cursors rather than offset-based pagination to prevent duplicate or missing records during concurrent inserts.
Security and State Authority
- [ ] Zero Client Trust: All authorization checks, pricing calculations, inventory reservations, and state machine transitions are executed exclusively on the backend.
- [ ] Session Lifecycle Management: Refresh token rotation and multi-device revocation flows operate correctly across extended mobile installation lifecycles.
- [ ] Safe Error Sanitization: Internal database errors, stack traces, and microservice topologies are stripped from external API responses and logged solely to secure internal observability stores.
Conclusion
Building a backend that powers both mobile and web products is not a question of choosing between monolithic REST, GraphQL, or gRPC. It is an architectural discipline that balances a unified domain core with specialized client edge adaptations.
The core domain services—responsible for data integrity, ACID consistency, financial transactions, and business invariants—must remain authoritative and decoupled from presentation concerns. Surrounding that core, client-specific layers such as the Backend for Frontend (BFF) pattern insulate the system from conflicting requirements: allowing web frontends to consume high-volume data loaders for rich desktop viewports, while providing mobile clients with aggregated, pruned, and battery-conscious endpoints.
By respecting the physics of cellular radio networks, planning for perpetual client version fragmentation, enforcing strict server-side authorization, and adopting robust offline sync protocols, engineering teams can build scalable backend platforms that deliver exceptional experiences across every device category.


