Why one backend beats three
When a product grows beyond a single web UI, teams often invent a second or third API “just for mobile” or “just for desktop.” That feels fast in the first sprint. Six months later you have duplicated auth rules, mismatched pagination, and bug fixes that only land on one client.
A single HTTP API with a clear contract scales better for most B2B products. Web, desktop, and mobile become different consumers of the same resources. Platform-specific work stays in the clients—navigation, offline caches, native notifications—not in three parallel business layers.
This article is about the design habits that keep that shared API honest. No product names, no proprietary schemas—only patterns you can reuse on any multi-client stack.
Start from resources, not screens
Screen-driven APIs (“getDashboardBundle”) couple the backend to one layout. The web team renames a card and suddenly mobile breaks. Resource-driven APIs (“contacts”, “orders”, “messages”) stay stable while each client composes screens differently.
Name endpoints after nouns your domain already uses. Prefer predictable verbs: list, get, create, update, delete, and a small set of explicit actions when a state machine needs them. Avoid inventing a new verb for every button in the UI.
When a screen needs many resources, let the client fetch in parallel or add a thin aggregate endpoint that is documented as a convenience—not as the only source of truth. Aggregates are allowed; they should never be the only place business rules live.
One contract, many transports
Treat JSON over HTTPS as the shared language. Document status codes, error shapes, and pagination once. Every client—React web, Electron renderer, React Native—should parse the same error body and the same page cursor.
Authentication belongs in the contract too. If the web app uses an HTTP-only session cookie, decide early whether desktop and mobile will use the same cookie jar model, a bearer token after login, or both. Mixed modes are fine if you document which clients use which mode and keep authorization checks identical on the server.
Version the contract deliberately. Prefer additive changes: new optional fields, new endpoints. Breaking changes need a version prefix or a sunset header and a migration window—especially when desktop installers update slower than the website.
What must stay client-specific
Push notification device tokens, local draft storage, and deep-link routing are client concerns. The API should accept a device registration payload and send a message; it should not know whether the UI is Electron or a phone.
File uploads are another split. Clients may use different pickers and size limits, but the API should expose one upload flow—presigned URL or multipart—with the same validation rules. Desktop might allow larger files; enforce limits in one place so security does not depend on the client being polite.
Real-time features (websockets, voice sessions) often need short-lived credentials. Issue those credentials from the shared API after the same auth check every client already passes. Do not embed long-lived secrets in desktop or mobile builds.
- Shared: domain resources, authz rules, validation, audit logs
- Client-owned: layout, offline queues, OS permissions, installers
- Issued by API, consumed by client: upload URLs, push setup, realtime tokens
Environment and secrets across clients
Web apps can read public config at runtime. Desktop and mobile builds often bake public API base URLs into the binary. That is normal—as long as private keys never ship in those bundles.
Use a fail-fast config on the server: if the database URL or required API key is missing, refuse to start. On clients, validate that the API base URL is present in development and that production builds point at the production host. Keep development proxies (for example Vite proxying to a local API) out of production builds.
Name client-exposed variables clearly so engineers do not accidentally put a server secret into a Vite or Expo public env slot. A short naming convention prevents more incidents than a long wiki page.
Versioning and release discipline
Desktop users may stay on a build for weeks. Mobile stores add review delay. The website updates instantly. Your API must tolerate that skew.
Additive fields with defaults keep old clients alive. When you must break a response shape, introduce a new path or a version header and keep the old path until analytics show the old clients are gone. Log which client version hits deprecated routes so you can schedule removal with evidence.
Smoke-test the same critical flows—login, list a core resource, create one record—from each client against staging. A shared Postman or scripted suite against the API catches drift faster than waiting for three UI teams to notice.
Pitfalls that create a second API by accident
The first pitfall is “temporary” mobile DTOs that never get deleted. Temporary becomes permanent, and now you maintain two shapes for one resource.
The second is silent nulls. One client treats a missing field as empty; another crashes. Prefer explicit empty arrays and documented optional fields.
The third is authorization shortcuts in one client only—for example hiding an admin button in the UI but forgetting the server check. Every privileged action must fail closed on the API regardless of which app called it.
The fourth is undocumented rate limits and payload size caps. Desktop users paste large payloads; mobile users are on flaky networks. Document limits and return clear 413 or 429 responses so clients can show useful errors.
A practical checklist
Before you add a second backend “for convenience,” walk this list. If most answers point to client work, keep one API.
- Can every client express its screens with the same resources?
- Is authz identical for the same user on every client?
- Are errors and pagination consistent?
- Will an old desktop build still work after today’s deploy?
- Are secrets only on the server?
- Do smoke tests hit the API from more than one client path?
Closing
One well-designed API is not less work than three thin ones—it is different work. You invest in contracts, versioning, and shared tests so product features ship once. Web, desktop, and mobile stay free to feel native without forking the business logic.
If you are growing from a single Next.js site into installable clients, start the shared contract early. It is much cheaper than untangling three APIs after the first year of production traffic.
Write the contract down—OpenAPI, a short markdown handbook, or typed client packages—and treat breaking changes as releases, not drive-by edits. The clients will thank you when the desktop build is three weeks behind the website.