The Server Side, Designed for a Phone Rather Than a Browser
A mobile client is not a small browser. It has a radio that costs battery every time you wake it, a connection that disappears in lifts, and versions of itself still installed on people’s phones three years after you shipped them. All three change how the API should be built.
Forty Small Requests Cost Far More Than One Large One
On a desktop, request count barely matters. On a phone the radio powers up for each burst of traffic and stays in a high-power state for seconds afterwards, so a chatty screen shows up in the battery settings with your app’s name against it.
One Screen, One Request
Endpoints are shaped around what a screen needs rather than around database tables. A home screen that needs a profile, three counters and a list gets one response, not five round trips that each have to finish before the interface stops looking broken.
Cursor Pagination, Not Page Numbers
Lists come back in pages keyed on a cursor, so scrolling never re-fetches what is already on screen and inserting a new record does not shuffle everything by one position and duplicate a row halfway down.
Send Less
List endpoints return the fields a list shows, not whole objects with every relation attached. Responses are compressed, images are resized server-side to the density the device asked for, and nothing is sent twice because a screen forgot it already had it.
Ask What Changed
Sync endpoints take a change token and return only what has moved since. Conditional requests let an unchanged response come back as a few bytes instead of a full payload. Over a month of daily use the difference is substantial.
Never Poll on a Timer
A request every thirty seconds to see whether anything happened is the classic way to drain a battery and get uninstalled. Changes arrive by push, and the app reconciles properly when it comes back to the foreground.
Fail in a Way the App Can Act On
Errors carry a stable machine-readable code as well as a message, so the app can tell the difference between retry this, sign in again, and tell the user something specific. A generic failure is why apps show the unhelpful something went wrong screen.
What a Round Trip Actually Looks Like
The shape most of the mobile backends we build share. Not every app needs every layer, but this is the order things sit in.
Screens render from local storage first, so nothing waits on the network to appear
Changes written locally and queued with an idempotency key, retried with backoff until accepted
Certificates the app may be pinned against, plus caching for anything genuinely public
Token validated, app version read, abuse limits applied per device rather than per address
Screen-shaped endpoints, version negotiation, and the place that keeps old app versions working
Your actual business logic, plus media uploaded straight to storage rather than through the API
A prompt to come and look, never the data itself, with delivery treated as best effort
Offline-First and the Conflict Question Nobody Wants to Answer
Deciding what happens when two people change the same thing is a product decision dressed as a technical one, and it has to be made before the code is written.
Local First, Always
The app writes to its own database immediately and shows the result, then syncs. The alternative — a spinner while the network decides — makes an app feel slow even on a good connection, because a good connection still costs a couple of hundred milliseconds you did not have to spend.
Delta Sync, Not Full Refresh
The app sends the token it last received and gets back what has changed since. Fetching everything on each launch is fine with fifty records and hopeless with fifty thousand, and the switch usually happens without anyone noticing until the complaints start.
Idempotency Keys on Every Write
A request that times out may still have been processed. Without a key that lets the server recognise a retry, the user taps once, the phone sends twice, and you get two orders. This is the most common real-world bug in apps that queue work offline.
A Conflict Rule Per Data Type
Last writer wins is fine for a status field and wrong for a running total, which should be merged. Anything with legal or financial weight should not be resolved automatically at all — it should stop and ask a person. One global rule is how somebody’s afternoon of work quietly disappears.
Deletes Need Tombstones
A record that simply vanishes from the server cannot be distinguished from one that was never sent, so deletions are recorded as deletions and cleaned up later. Otherwise deleted items reappear on the next sync, which looks like the app is haunted.
The Local Schema Migrates Too
The device database has its own versions, and an update that changes its shape has to migrate whatever the user has, tested against real data. You cannot roll that back once the update is installed, which makes it one of the few genuinely irreversible steps in mobile.
Field and workforce apps lean on this hardest — there is more on how it plays out in practice on the enterprise mobile apps page.

Notifications Are a Prompt, Not a Transport
Push goes out through Apple’s service for iOS and Google’s for Android, and both describe delivery as best effort. A message can be dropped after a device has been offline for a long time, delayed by power saving, discarded because the user turned that category off, or blocked outright by a manufacturer’s battery management.
So the state always lives on the server. The notification says come and look; the app reconciles when it opens. Anything that only exists inside a notification is something a share of your users will never see.
- Token-based credentials for Apple rather than certificates that expire on a date nobody diarised
- Device tokens rotate, so they are refreshed on launch, de-duplicated, and pruned when the service reports them dead
- Android channels so people can silence one kind of message instead of switching all of them off
- Silent background pushes used sparingly, because both platforms throttle them and neither guarantees them
- Every notification deep-links to the exact screen it is about, in the right account, including from a cold start
- Sent in the recipient’s time zone, with a rate limit, because the fastest route to a mass opt-out is three in one evening
Two Things That Behave Differently on a Phone
Sessions That Survive Days Offline
- A short-lived access token for requests and a longer-lived refresh token used only to get new ones
- Both stored in the platform secure storage, never in ordinary preferences and never written to a log
- Refresh tokens rotate on use, and a reused one is treated as a compromise and revoked
- Biometrics unlock the local session rather than calling the server, because it has to work with no signal
- A refresh that fails mid-shift drops to a read-only state that keeps the queued work rather than signing the user out and losing it
- Sign out everywhere is a server-side revocation, not merely clearing the token on one device
- No secret that matters is shipped inside the binary, because anything in an app can be extracted from it
Uploads on a Connection That Keeps Dropping
- Images downscaled and re-encoded on the device first, because a modern phone camera produces files nobody needs at full size
- Uploaded straight to object storage using a short-lived signed URL, so your API is not acting as a pipe for large files
- Chunked and resumable, so a dropped connection continues from where it stopped instead of starting again
- Handed to the platform background transfer mechanism, so it keeps going when the person leaves the app
- Content hashed so a retried upload does not create a second copy
- Progress, pause and cancel visible in the interface, and a queue the user can see rather than guess at
- Thumbnails and derived sizes generated server-side, so the app never downloads a full-resolution image to show a list
Your Old App Versions Are Going to Outlive Your Patience
On the web there is one version of the product. In the stores there are as many versions as people have declined to update, and some of them will still be making requests in three years.
Additive Changes by Default
Add fields, never remove them and never change what one means. A field that changes type between releases breaks every installed copy that was parsing it, and those copies cannot be fixed retrospectively.
Tolerant Clients
The app ignores fields it does not recognise and copes with ones that are missing, so a server change does not require a coordinated release. This is a decision made in the first week of the build or not at all.
Every Request Says Who Is Calling
App version, platform and build travel with each request. Without that you cannot answer whether it is safe to retire an endpoint, and you are guessing about who a change would break.
A Minimum Version Gate
The server can tell an app it is too old to continue, and the app shows a clear update screen rather than failing strangely. Built in from version one, because adding it later cannot help the versions already installed.
Retire on Evidence
Deprecations are decided from the actual version distribution, with a warning period and, where it matters, an in-app prompt first. Turning something off because it is old is how you find out which customer was still using it.
Feature Flags Keyed on Version
New server behaviour can be enabled only for builds that understand it, which lets the backend move ahead without waiting on a store review cycle.
This is also why release discipline on the app side matters — the staged rollout described on the MVP page exists partly to keep the number of live versions manageable.
Firebase or Supabase Versus Your Own Backend
We build both and have no preference to defend. The trade-off is real in each direction and it depends on your data, not on your ambition.
Managed Platform
Fast to Start, Constrained Later
- Authentication, storage, push and a database working in days, with no servers to run
- Offline behaviour and realtime updates provided rather than built
- Crash reporting and analytics in the same place, which suits an MVP
- Cost tracks reads and writes, so one careless screen can be expensive at scale
- Complex queries, reporting and relational work get awkward
- Access rules live in a rules file that gets hard to reason about as roles multiply
- The client SDK reaches deep into the app, so leaving later is a real project
Custom Backend
Slower to Start, Yours Afterwards
- A data model that fits your domain rather than one that fits the platform
- Server-side logic, scheduled work and integrations with systems you already run
- Predictable cost as usage grows, and no surprise from a chatty screen
- Answers for procurement about where data lives and who can reach it
- Weeks of work before the app has anything to talk to
- Somebody has to run it, patch it and be woken by it
- Offline sync, realtime and push all have to be built rather than switched on
The answer we give most often is neither extreme. Use the managed platform for the parts it does well — authentication, push, file storage, crash reporting — and keep your own API for the business logic that is genuinely yours and that you would hate to have expressed in a rules file. That keeps the fast start without painting the app into a corner, and it means the expensive part of a future migration is small.
Backend Questions From App Projects
Can you build the backend too, or work with the one we already have?
Either. Where a backend already exists we prefer to keep it and add a thin mobile-facing layer in front of it, so the app gets endpoints shaped for a screen without your existing systems being rewritten. Where there is nothing yet, we build the backend alongside the app so the two are designed together rather than negotiated across a boundary. What we will not do is quietly leave your app talking to something nobody owns: whichever route we take, the deployment, the environment configuration and the credentials end up documented and in your accounts.
Do we need a custom backend or would Firebase be enough?
For a first version with fairly simple data, a managed platform such as Firebase or Supabase gets you authentication, storage, push and a database in days rather than weeks, and there is no infrastructure to run. That is a genuine advantage and we use them where they fit. They become uncomfortable when your data is strongly relational, when reporting gets complicated, when the pricing model punishes a read pattern you cannot easily change, or when procurement asks questions about data location and control. The middle route is common and works well: a managed platform for authentication, push and file storage, with your own API for the business logic that is actually yours.
What does the app do when the phone has no signal?
That depends on a decision made at design time rather than on anything that can be added later. In an offline-first design the app keeps its own database on the device, reads every screen from that, writes changes locally first and syncs in the background, so losing signal changes nothing visible except a sync indicator. In a simpler design the app caches recent data so it can still show something, but new work needs a connection. Offline-first costs more to build and is the right answer whenever people use the app somewhere with unreliable coverage or would lose real work if a request failed.
Are push notifications guaranteed to arrive?
No, and any design that assumes otherwise will eventually cost you. Both Apple and Google describe their push services as best effort. Messages can be dropped when a device has been offline for a long time, delayed by power-saving behaviour, silently discarded when the user has turned the category off, and on some Android handsets blocked by aggressive manufacturer battery management. So push is a prompt, never a transport: the actual state always lives on the server, the app reconciles when it opens, and anything important also appears somewhere in the app rather than only in a notification the person may never see.
What happens to users who never update the app?
They keep using whatever version they installed, sometimes for years, and your API has to keep serving them. That is why we treat every API change as additive by default: new fields are added, existing ones are never removed or given a different meaning, and the app is written to tolerate fields it does not recognise. Where a genuine breaking change is unavoidable, a new version runs alongside the old one and we look at the real version distribution before retiring anything. We also build in a minimum-supported-version check from day one, so in the rare case where an old build must stop, it shows a clear update screen instead of failing in a confusing way.
Where are login tokens kept on the device?
In the platform secure storage — the keychain on iOS and the keystore-backed equivalent on Android — never in ordinary application preferences or a plain file, and never written into logs. The app holds a short-lived access token used for requests and a longer-lived refresh token used only to obtain new ones, so a leaked access token expires quickly. Refresh tokens rotate on use, and a reused one is treated as a compromise and revoked server-side. Where the app is protected by biometrics, that unlocks the local session rather than authenticating against the server, because the whole point is that it has to work with no connection.
Our API was built for a website. Can the app just use that?
Sometimes, and it is always worth checking before spending money. The problems usually appear in three places. A web API tends to be chatty, because on a browser ten small requests over a fixed connection cost little, whereas on a phone each one wakes the radio and is paid for in battery and data. It often assumes cookies and a browser session rather than tokens. And it frequently returns whole objects when a list screen needs four fields, which on a mobile connection is noticeable. The usual answer is not to rebuild anything but to put a small mobile-facing layer in front of it that batches, trims and paginates for the screens the app actually has.
What the Backend Sits Underneath
Enterprise Mobile Apps
Where offline sync, conflict rules and directory-backed sign-on stop being optional and become the requirement.
Learn moreSecurity & Privacy
Token handling, attestation, pinning and what the store data declarations have to match.
Learn moreTesting & QA
Testing an app against a slow connection, a failing endpoint and an expired token, rather than only against a healthy server.
Learn moreSend Us the Server-Side Half of the Problem
An existing API, a Firebase project, or nothing at all. You get a recommendation, a scope and a cost range within two business days.