API gateway vs BFF for web and mobile clients: tradeoffs
API gateway vs BFF: learn how each pattern affects versioning, performance, and separation of public and internal endpoints for web and mobile apps.

The problem: one backend, many clients, changing needs
A common starting point is simple: one backend exposes a set of endpoints, and both the web app and the mobile app call them. It feels efficient because there is one place to add features, fix bugs, and enforce rules.
Then reality hits. The web UI often needs dense screens, filters, exports, and admin actions. Mobile usually needs fewer fields, faster screens, offline-friendly flows, and careful battery and data usage. Even when the feature is "the same", the best API shape for each client is rarely identical.
Over time, endpoints drift. A web team adds extra fields for a new table view. Mobile asks to remove heavy payloads and combine multiple calls into one. Someone adds a parameter "just for iOS". Another person reuses an internal admin endpoint in the public app because it was "already there". What started as one clean API becomes a collection of compromises.
The risks show up fast:
- Breaking changes when one client ships faster than the other
- Slower apps from large payloads or too many round trips
- More complicated backend code as every endpoint tries to serve every client
- Accidental data exposure when internal fields or actions leak into public APIs
- Painful versioning because "small changes" are not small to older clients
This is the core tension behind the API gateway vs BFF discussion. You want stable public APIs that mobile and web can rely on, while still giving each client room to evolve at its own pace.
API gateway and BFF: what they are (and are not)
An API gateway is a front door for your APIs. Clients call the gateway, and it routes requests to the right backend service. It often handles shared concerns you do not want to repeat everywhere, like authentication checks, rate limits, request logging, and basic request shaping.
A backend-for-frontend (BFF) is a small backend built for one specific client or client group, like "web" and "mobile". The client calls its BFF, and the BFF calls the underlying services. The key idea is focus: the BFF is allowed to speak the client's language (screens, flows, and payloads), even if the core services stay more generic.
What these patterns are not: they are not replacements for good domain services or a clean data model. Your core services, databases, and business rules should remain the source of truth. A gateway or BFF should not become a giant blob of business logic that slowly turns into your real backend.
A simple way to tell them apart:
- Gateway: one entry point, shared concerns, routing and protection
- BFF: client-specific APIs that reduce client work and hide internal complexity
You can also combine them. A common setup is a gateway as the public edge, then separate BFFs behind it for web and mobile. The gateway handles outer security and traffic rules, while each BFF shapes endpoints and responses for its client.
How the request path changes in each pattern
The biggest difference between an API gateway and a BFF is where you put the "front door" logic: routing, authentication checks, and response shaping.
With an API gateway, the client usually talks to one shared entry point. The gateway forwards requests to internal services, often doing basic tasks like token validation, rate limits, and routing by path.
With a BFF, each client type (web, iOS, Android) calls a backend built specifically for it. That BFF then calls internal services and returns a response tailored to that client's screens and constraints.
A simple way to picture the request path:
- API gateway path: Client -> Gateway -> Service(s) -> Response
- BFF path: Client -> BFF (web or mobile) -> Service(s) -> Response
Ownership often shifts too. A platform or infrastructure team typically owns the gateway because it affects every team and every service. A feature team often owns a BFF because it moves with the UI and its release cycle.
Authentication commonly flows like this: the client sends a token, the edge layer (gateway or BFF) validates it or passes it to an auth service, and then forwards identity details (user id, roles) to downstream services. The difference is where you apply client rules. With a gateway, policies are usually generic and consistent across clients. With a BFF, you can add client-specific shaping, like returning a smaller payload for mobile or combining multiple service calls into one response on slow networks.
That shaping step is where BFFs shine, but it also means more moving parts to deploy and keep consistent.
Separating public vs internal endpoints safely
A public endpoint is any API route your web app, mobile app, partners, or third parties can reach. Treat it as hostile by default, because you cannot control the network it travels over or the client code that calls it.
An internal endpoint is meant for service-to-service traffic inside your system. It can change faster, assume more context, and expose richer data, but it should never be directly reachable from the public internet.
With an API gateway, the split is often physical and easy to reason about: only the gateway is exposed, and it decides which external routes exist. Everything behind it stays private. You can keep internal service APIs expressive, while the gateway enforces a smaller, safer surface area.
With the backend-for-frontend pattern, the split is more about product boundaries. Each client (web, iOS, Android) talks only to its BFF, and the BFF talks to internal services. That lets you hide internal complexity: the BFF can call three services, merge results, and expose one simple response that matches what the client actually needs.
The separation is only safe if you add practical controls:
- Specific authorization: roles and scopes per route, not one "logged in" switch
- Rate limits per user, token, and IP for public endpoints
- Payload filtering: return only what the client needs, strip internal IDs, debug info, and admin-only fields
- Clear allowlists: which endpoints are public, which are internal-only
Example: a mobile app needs a "My Orders" screen. The BFF can expose /orders with only order status and totals, while the internal order service keeps detailed cost breakdowns and fraud flags private.
Versioning: what gets easier and what gets harder
Versioning pain usually shows up when web and mobile move at different speeds. A web team can ship and roll back in hours. A mobile app can take days or weeks because of app store review and users who do not update. That gap is where the API gateway vs BFF decision becomes practical.
With an API gateway, you can put versioning at one front door (for example, /v1/..., /v2/...). This is easy to explain and easy to route. The downside is that the gateway can turn into a version museum if many clients and partner integrations stick to old versions. You end up supporting old shapes of the same data for longer.
With a BFF, versioning is often "per client". The mobile BFF can stay on an older contract while the web BFF moves faster, even if both talk to the same internal services. That usually reduces pressure to keep public versions alive forever. The tradeoff is more moving parts: you now have multiple version decisions to manage and deploy.
Versioning inside services can work too, but it pushes client-driven changes deep into your system. It can also make internal code harder to read because service logic starts branching on client versions.
Non-breaking changes are your best friend: adding optional fields, adding new endpoints, or accepting extra input fields. Breaking changes include renaming a field, changing a type (string to number), or removing an endpoint.
Deprecation works best when it is planned:
- Set a clear sunset date and communicate it early
- Track usage of the old version (logs, metrics) and watch for stragglers
- Roll out client updates first (especially mobile), then remove the old path
- Return a clear error message when an old version is finally blocked
Performance: latency, payload size, and number of calls
Performance in an API gateway vs BFF setup is mostly a trade: one extra hop inside your system versus fewer hops over the network from the client. The fastest option is often the one that reduces slow, unreliable client network time, even if it adds a small server-side step.
A BFF often wins when the client would otherwise make many calls. Instead of web and mobile calling five endpoints and stitching results on the device, the BFF can fetch what it needs server-side and return one response. That usually cuts total latency on mobile because cellular networks add delay on every request.
Common performance wins from a gateway or BFF:
- Aggregation: combine data from multiple services into one response
- Smarter caching: cache at the edge or at the BFF for read-heavy screens
- Smaller payloads: return only fields the screen needs
- Fewer round trips: reduce chatty client behavior
- Consistent compression and timeouts: enforce defaults in one place
But the pattern can also hurt. Every added layer adds CPU work and more places to wait. If you duplicate the same aggregation logic for web and mobile, you may double the work and create inconsistent behavior. Over-fetching is another common loss: a generic endpoint that tries to satisfy every screen can return large payloads that waste time and bandwidth.
Mobile realities make these tradeoffs sharper. Flaky networks mean retries and timeouts are normal, and each extra call burns battery. Cold starts matter too: if the app needs several requests before the first screen is usable, users feel it.
A practical rule: optimize for fewer client requests first, then optimize the added hop.
Quick way to judge a design
If a screen needs more than 2-3 sequential calls, consider aggregation. If responses are large and mostly unused, consider splitting endpoints or using a BFF tailored per client.
Operations: deployment, monitoring, and scaling
With API gateway vs BFF, the big operational question is how many moving parts you are willing to run and support. A gateway often becomes shared infrastructure for many teams and clients. BFFs are usually smaller services, but you may end up with one per client (web, iOS, Android, partner), which increases release and on-call load.
On testing and releases, a gateway can be safer when you mainly need routing, auth, and rate limits. Changes are centralized, so one mistake can affect everyone. BFFs reduce blast radius because a web-only change ships to the web BFF, not the mobile one. The tradeoff is more pipelines to maintain and more versions in flight.
For observability, you need to see a single user request across layers, especially when one mobile call triggers several backend calls.
- Use a correlation ID and pass it through gateway, BFF, and backend logs
- Capture traces so you can spot where time is spent (gateway, BFF, downstream service)
- Keep structured logs (client, endpoint, status code, latency, payload size)
- Track a few key metrics per endpoint: error rate, p95 latency, throughput
Scaling looks different too. A gateway is a shared choke point: it must handle spikes and hot endpoints without becoming a bottleneck. BFFs let you scale per client, which helps when web traffic jumps during business hours while mobile is steady.
In incidents, failures can "move" depending on the pattern. With a gateway, problems often show up as widespread 5xx or auth failures. With BFFs, issues may be isolated to one client's feature. Make runbooks clear about where to look first, and keep fallback behavior simple (for example, return a reduced response instead of timing out).
How to choose: a simple step-by-step decision process
Choosing between an API gateway and a BFF is less about theory and more about what your clients need day to day.
A practical decision flow
-
Start with your clients, not your servers. Write down every client you support (web app, iOS, Android, partner API, internal admin) and list what each key screen needs. If the same "Customer details" view needs different fields and different call patterns across clients, that is a strong signal for client-specific shaping.
-
Map what you have today. Take your current endpoints and mark what is shared (core domain operations like orders, payments, users) versus what is presentation-shaped (a dashboard summary, a combined "home screen" payload). Shared pieces belong in the core backend. Presentation-shaped pieces usually fit better in a BFF.
-
Decide where shaping and rules should live. If you mainly need routing, auth, rate limits, caching, and safe exposure of public vs internal endpoints, a gateway is the natural home. If you need real composition (calling multiple services, turning six calls into one, different payloads per app), put that logic in BFF code so it stays testable and readable.
-
Pick a versioning and deprecation rule you can actually follow. For example: "No breaking changes without a new version, and every deprecated field lives for 90 days." With a gateway-only approach, you may end up versioning the public surface and translating behind it. With BFFs, you can often keep the core APIs stable and version only the BFF endpoints per client.
-
Plan rollout and measure. Before changing anything, capture baseline metrics: p95 latency, number of calls per screen, payload sizes, and error rates. Roll out to a small percentage first, compare before and after, then expand.
A simple example: if your mobile app releases monthly but your web portal ships daily, a small mobile BFF can protect the app from frequent backend changes while the web client keeps moving fast.
Example: web portal + mobile app with different release speeds
Picture a company that has a customer portal on the web and a field app on mobile. Both need the same core data: customers, jobs, invoices, and messages. The portal changes weekly. The mobile app updates slower because it must pass app store review and it also needs to work with weak signal.
The pain shows up fast. Mobile users want compact responses, fewer calls, and flows that support offline work (for example, download today's jobs once, then sync changes later). The web portal is fine making more calls and loading richer screens because it is always online and easier to update.
Option A: an API gateway in front of stable services
The gateway-first choice keeps your backend services mostly unchanged. The gateway handles authentication, routing, and small tweaks like headers, rate limits, and simple field mapping.
Versioning stays mostly at the service API level. That can be good: fewer moving parts. But it also means mobile-specific changes often push you toward broad versions like /v2 because the underlying endpoints are shared.
Endpoint exposure is clearer if you treat the gateway as the only public door. Internal endpoints stay behind it, but you must be strict about what the gateway can reach and what it publishes.
Option B: a mobile BFF that speaks "mobile"
With a mobile BFF, the mobile app talks to endpoints designed for mobile screens and sync flows. The BFF can aggregate data (job details + customer + last message), trim fields, and return one payload that matches the app's needs.
What changes:
- Versioning becomes easier per client: you can version the mobile BFF without forcing the web portal to move
- Performance often improves for mobile: fewer round trips and smaller responses
- Public vs internal separation gets sharper: the BFF is public, but it calls internal services that never need to be exposed
Common mistakes and traps to avoid
The biggest trap in the API gateway vs BFF debate is turning the gateway into a mini backend. Gateways are great at routing, auth, rate limits, and simple request shaping. When you pack them with business rules, you get hidden logic that is hard to test, hard to debug, and easy to break during a config change.
BFFs can go wrong in the opposite direction: teams create one BFF per screen or per feature, and maintenance explodes. A BFF should usually map to a client type (web, iOS, Android) or a clear product area, not every UI view. Otherwise you end up duplicating the same rules in ten places, and versioning becomes a full-time job.
Versioning mistakes often come from extremes. If you version everything on day one, you freeze your API too early and keep old variants alive forever. If you never version, you eventually ship breaking changes without meaning to. A simple rule works well in practice: do not version for small additive changes, but do version when you remove something or change meaning.
Public vs internal endpoints is where teams get hurt. Exposing internal services directly to the internet (even "temporarily") turns every internal change into a potential outage or security incident. Keep a clear boundary: only the gateway or BFF should be public, and internal services should stay private.
Performance issues are usually self-inflicted: oversized payloads, too many round trips, and no budget for latency. For example, a mobile app might only need order status and totals, but it gets the full order object with every line item and audit fields, making each request slow on cellular.
Warning signs to watch for:
- Gateway configs reference business concepts like "refund eligibility" or "VIP rules"
- BFFs multiply faster than the clients they serve
- You cannot explain your API versioning strategy in one sentence
- Internal service endpoints are reachable from the public internet
- Responses keep growing because "it might be useful later"
Quick checklist and next steps
If you are stuck on the API gateway vs BFF decision, focus on what will break first in real life: releases, payloads, and security boundaries.
Quick checklist
If you answer "no" to several of these, your current setup will likely hurt as your clients grow:
- Can you change one backend service without forcing every client to update the same week?
- Is there a clear boundary between public endpoints (safe for the internet) and internal ones (only for trusted systems)?
- Do web and mobile receive only what they need (not a huge "kitchen sink" response)?
- Can you roll out changes gradually (small percentage first) and see errors, latency, and unusual traffic quickly?
- Do you know who owns the contract for each endpoint and who approves breaking changes?
Next steps
Turn the answers into a plan. The goal is not perfect architecture, but fewer surprises when you ship.
Write your API contract in plain language (inputs, outputs, error codes, what is allowed to change). Pick an ownership model: who owns client needs (web/mobile) and who owns core domain services. Decide where versioning lives (per client, or centrally) and set a deprecation rule you will follow.
Add basic monitoring before big refactors: request rate, p95 latency, error rate, and the top endpoints by payload size. Prototype the riskiest client flows first.
If you're building with AppMaster (appmaster.io), one practical approach is to keep core business logic and data models in the generated backend, then add a thin gateway or BFF layer only where a client truly needs different payload shaping or release isolation.
If the checklist feels hard to answer, take that as a signal to simplify the contract and tighten the public vs internal split before adding more endpoints.
FAQ
Start with an API gateway when you mainly need one public entry point with shared controls like authentication checks, rate limits, and routing. Add a BFF when web and mobile need noticeably different payloads, fewer client calls, or independent release cycles.
A gateway is best for cross-cutting concerns and traffic control at the edge, so keep it focused on routing, auth enforcement, and basic request/response handling. A BFF should do client-facing composition like combining multiple service calls and trimming fields, but it still shouldn’t become the place where your core business rules live.
A gateway gives you one versioned “front door,” which is simple to explain but can leave you supporting old versions for a long time. A BFF lets you version per client, so mobile can stay stable while web moves faster, at the cost of maintaining more services and contracts.
Prefer non-breaking changes by default, like adding optional fields or adding new endpoints. Create a new version when you remove fields, rename fields, change types, or change meaning, because mobile users may not update for weeks.
Keep internal services private and expose only the gateway or BFF to the public internet. Filter responses so clients only get what they need, and enforce per-route authorization so an internal admin action can’t be reached just because a user is logged in.
Use a BFF when the client would otherwise make many sequential calls, because one server-side aggregation response is often faster than several mobile round trips. A gateway adds an extra hop too, so keep it lightweight and measure latency and payload sizes to avoid hidden slowdowns.
A gateway is a shared choke point, so a bad config or outage can affect every client at once. BFFs reduce blast radius by isolating changes to one client, but you’ll run more deploys, more monitoring, and more on-call surface area.
Use a correlation ID and pass it through the gateway/BFF and all downstream services so one user action is traceable end to end. Track a small set of per-endpoint metrics like error rate, p95 latency, throughput, and payload size so performance regressions show up quickly.
A common trap is letting the gateway accumulate business rules, which makes behavior hard to test and easy to break with a config change. Another is creating too many BFFs (for every screen), which duplicates logic and makes versioning and maintenance painful.
Keep core data models and business processes in the backend you generate, then add a thin gateway or a client-specific BFF only where you truly need shaping or release isolation. In AppMaster, this usually means building stable domain endpoints in the generated Go backend and adding a small layer only for mobile-friendly aggregation or payload trimming.


