Backend-for-frontend layer for web and mobile apps
A backend-for-frontend layer helps web and mobile apps get the data they need while you keep business rules in one backend.

Why one API gets messy
A single API feels neat when a product is small. One contract is easier to explain, test, and change. Then the product grows.
The web app starts asking for full tables, filters, totals, and extra metadata. The mobile app wants smaller payloads, fewer requests, and screens that load fast on a shaky connection. Both clients trigger the same business actions, but they don't need the same response.
That gap creates friction fast. A generic endpoint sends too much data to one client and still leaves holes for the other. The web team gets fields it never renders. The mobile team still needs one more status flag, one computed label, or a shorter list for a small screen, so it makes extra calls and stitches results together in the app.
Once that starts, frontend code fills up with rules that don't belong there. One client decides when an order is late. Another uses a slightly different cutoff. A discount shows on the web but not on mobile because each app rebuilt the same check in its own way. Teams do this because they need to ship, not because it's a good design.
After a few releases, the product drifts. The web app sorts items one way. The mobile app groups them another way. A bug gets fixed in iOS but not on the web. Support sees different answers for the same account, and debugging turns into a scavenger hunt.
A dashboard makes the problem easy to see. On the web, a user may need totals, trend data, filters, and a full activity list. On mobile, the same person may need three alerts, today's total, and one clear next step. If one backend tries to satisfy both with the same response, it usually lands in the middle and serves neither very well.
That's the point where a backend-for-frontend layer starts to make sense.
What a backend-for-frontend layer does
A backend-for-frontend layer, or BFF, sits between the client and your main backend. Its job is simple: shape data for the client that asks for it.
The web client can get full tables, filters, and extra metadata for a wide screen. The mobile client can get fewer fields, smaller payloads, and a response built for a shorter flow. The BFF handles that translation so the frontend doesn't have to.
It can also combine several backend calls into one response. Instead of making the app request user details, account status, recent activity, and feature flags one by one, the BFF can gather them and return one clean object. That removes glue code from the client and often makes screens feel faster.
What stays in the main backend
This is where teams often get into trouble. A BFF should shape data for presentation. It should not become the place where business rules live.
Pricing, permissions, approval rules, discount logic, and workflow state should stay in the main backend, where every client uses the same source of truth. The BFF can rename fields, trim unused data, merge responses, or adjust pagination for a mobile screen. It should not decide who gets a refund or whether a user can approve a payment.
Think of a product with a web admin dashboard and a mobile customer app. Both depend on the same order rules. The dashboard needs totals, filters, audit details, and staff actions. The mobile app needs order status, delivery time, and the next step for the customer. A BFF can return both views cleanly while the shared backend keeps the actual order rules in one place.
That split is worth protecting. Frontend teams move faster, and backend behavior stays consistent.
When this pattern fits
A BFF is useful when you ship the same product on web and mobile, but each client needs the data in a different shape. The business rules stay shared. The payload size, screen flow, and request pattern do not have to.
Mobile usually exposes the problem first. A desktop app can survive a heavier response and a few extra calls. A phone app pays for that with slower screens, more loading states, and more battery use. If the mobile team keeps asking for smaller payloads while the web team keeps asking for richer responses, one shared API often turns awkward.
This pattern also helps when the frontend changes often. Screens move around. A checkout step gets merged. A dashboard card suddenly needs data from three services. If every small UI change has to wait for the main backend, the backend becomes a bottleneck.
The clearest warning sign is simple: fixing one client keeps making the other worse. You add fields for the web app, and the mobile app gets slower. You simplify a response for mobile, and the web team has to make extra calls. That is usually a sign that one API is trying to serve two very different jobs.
It's a good fit when:
- web and mobile show the same business process in different ways
- mobile performance matters enough that payload size really hurts
- frontend releases happen more often than backend releases
- you want screen logic to change without moving domain rules out of the shared backend
An account page is a common example. The web version may load profile data, recent activity, invoices, and admin options in one rich view. The mobile version may need only the profile and the last few actions in one small response. If your product keeps producing differences like that, a BFF is probably worth trying.
How to set it up
Start with screens, not endpoints. Put the web and mobile flows next to each other and write down what each screen needs on first load, after a user action, and when something fails. That usually exposes the real gaps fast.
Then separate presentation logic from business rules. Presentation logic covers field names, grouping, sort order, image sizes, and whether a screen needs a summary or full details. Business rules cover permissions, pricing, stock checks, validation, and status changes. Keep those rules in shared services before you add any client-specific endpoint.
A practical setup usually looks like this:
- Write a small data contract for each screen instead of one giant contract for the whole app.
- Move shared business decisions into backend services before building client-specific responses.
- Add thin BFF endpoints only where web and mobile truly need different shapes.
- Reuse the same auth, logging, and caching approach across the whole request path.
- Measure payload size, latency, and error paths from day one.
Thin is what matters most. A BFF should shape data, join a few calls, and remove client-side glue code. If it starts owning rules, workflows, or policy decisions, you've built a second backend by accident.
Watch the numbers early. If a mobile payload drops from 180 KB to 45 KB, users notice. If one screen still takes 1.8 seconds because the BFF calls four services in sequence, fix that before launch. The extra layer should make clients simpler, and it should make failures easier to trace.
A simple example with web and mobile
An online store is a good example because web and mobile shopping habits are different.
On the website, people browse longer, compare products, read details, add promo codes, and move through a full checkout. On mobile, they usually want speed. They search, tap, reorder, and leave.
That creates pressure on one general API. The web app asks for product filters, comparison data, promo blocks, saved carts, and full checkout details. The mobile app doesn't want most of that. It wants a short payload, quick search results, recent orders, and a checkout flow with fewer steps.
A BFF fixes that without splitting business logic in two places. The web side can prepare a response with category filters, product badges, comparison fields, and checkout details. The mobile side can return a much smaller response with search suggestions, reorder buttons, and just enough data to finish a purchase on a small screen.
Both clients still rely on the same order service underneath. That service keeps the rules that affect money and state: tax calculation, stock checks, payment rules, discount validation, and order status changes.
Picture someone buying coffee pods. On the website, they compare pack sizes, read delivery terms, add a promo code, and review a detailed summary before paying. On mobile, the same person opens the app during a commute, taps "buy again," confirms the saved address, and pays in seconds. The rules stay the same. Only the response shape and screen flow change.
That's the whole point. Web developers don't have to drag around mobile shortcuts they never use, and mobile developers don't need heavy web checkout data just to show a reorder button.
Where to draw the line
A BFF should shape data, not invent business truth. If it starts deciding prices, stock, or account permissions, you no longer have one system of record. You have two versions of the same product logic, and those versions will drift.
The BFF is the right place to rename fields, group related data, hide extra detail, and format values for a specific screen. A web order page may need a full breakdown with notes, tax lines, and admin actions. A mobile app may need the same order packed into a short summary card with cleaner labels and fewer sections. That's fine.
Changing the rules behind the order is not.
A simple rule helps: if a value affects money, access, inventory, or state, keep it in the core backend. The BFF can present that value in a friendlier way, but it should not recalculate it.
That boundary also makes future clients easier to build. If you later add a partner portal, a tablet app, or a new checkout flow, each one can get a response that fits its layout while the actual business behavior stays shared.
Common mistakes teams make
The biggest mistake is letting the BFF turn into a second backend. At first it feels harmless. One small exception lands there, then another, and soon it owns its own permissions, workflow checks, and data rules. After that, every bug fix starts happening twice.
Another common problem is endpoint copying. Teams create /orders-web and /orders-mobile, clone most of the same code into both, and promise to clean it up later. Later rarely comes. Every field change becomes more edits, more tests, and more chances to miss one path.
Operations get messy too. One team owns the core API, another owns the BFF, and nobody owns the full request path. Latency grows a little at each hop. Logs live in different places. Error messages change shape between layers. When users hit a timeout, every team says its own service looks fine.
A few warning signs show up early:
- the BFF stores rules the main backend doesn't know about
- two endpoints differ by only a handful of fields
- teams argue about which layer should return an error
- web and mobile users see different answers for the same account
Set one owner for latency budgets, tracing, and error formats across the whole chain. That sounds boring, but it saves a lot of pain later.
Checks before you launch
A BFF can look tidy on a diagram and messy under real traffic. Before you put it in front of users, test the plain details.
Start with the response itself. Open real screens on web and mobile, then inspect the payloads. Each client should get only the fields it renders or needs for the next action. If the mobile app still pulls desktop-only data, your response shaping is still loose.
Then test a small rule change. Change something simple, like who can see a discount or when a button should stay disabled. Update that rule once and confirm that both clients follow it without separate fixes. If one screen still needs custom logic in the frontend, your shared business rules are not really shared.
Tracing is the other hard check. When a request fails, your team should follow it from the client to the BFF and then to the main backend without guessing. Request IDs should match. Logs should tell one story. Error messages should point to the right layer.
Before launch, make sure:
- web and mobile can change layout without moving business logic into the client
- teams know which fields each client still uses and which ones they can remove
- one broken dependency shows up fast in logs and monitoring
- the frontend can ask for a new screen shape without changing domain rules
If one of those checks fails, stop and fix it. A week spent tightening the boundary is usually cheaper than months of patching two clients that keep drifting apart.
What to do next
Pick one flow that already hurts: a slow checkout, a fragile account screen, or a mobile page that makes too many API calls. Start there. A BFF should solve a real problem, not become a side project.
Keep the first version narrow. Add it only where web and mobile truly differ in payload size, data grouping, or request timing. If both clients can use the same response with small UI changes, leave that endpoint in the shared API and move on.
Write the boundary down before anyone codes. Pricing, permissions, and workflow rules stay in the shared backend. Field selection, response formatting, and combining calls for one screen can live in the BFF. Any rule that has to match across every client needs one owner.
Then measure the result. Check response time, payload size, client code removed, and bug count for that one flow. If the numbers barely move, stop there. In plenty of cases, cleaning up a messy endpoint does more good than rolling out the BFF pattern everywhere.
If the tradeoff still feels fuzzy, a short architecture review can save you from owning the wrong layer for the next two years. Oleg Sotnikov at oleg.is works with startups and small teams on product architecture, backend boundaries, and infrastructure, so this is the sort of decision he deals with regularly. Sometimes the answer is a BFF. Sometimes it's one cleaner API and less code to maintain.
Frequently Asked Questions
What is a backend-for-frontend layer?
A BFF sits between your client and your main backend. It takes shared backend data and returns a response that fits one client, like a rich web screen or a smaller mobile screen.
When do web and mobile need separate responses?
Use it when web and mobile show the same business process in different ways and one shared response keeps hurting both sides. If mobile needs lean payloads while web needs more detail, a BFF often fixes that split.
Does a BFF mean I need two backends?
No. Keep one core backend for pricing, permissions, stock, and status changes. Let the BFF only shape fields, combine calls, and trim data for each client.
What logic should stay out of the BFF?
Keep anything that affects money, access, inventory, or workflow state out of the BFF. The BFF can rename fields and group data, but it should not decide refunds, discounts, approvals, or who can do what.
Will a BFF make my app faster?
It can, but only if you design it well. A BFF often speeds up screens by cutting payload size and merging several backend calls into one client response, but slow chained calls inside the BFF will cancel that gain.
How do I start without making a bigger mess?
Start with one painful flow, not the whole product. Put the web and mobile screens side by side, write down what each one needs, move shared rules into the core backend, and add thin BFF endpoints only where the response shape truly differs.
What is the clearest sign one API no longer fits both clients?
Watch for a simple pattern: fixing one client keeps making the other worse. If web asks for more fields and mobile slows down, or mobile asks for a leaner response and web needs extra calls, one API no longer fits both jobs.
Should I build a BFF for every endpoint?
No. Build it only for screens where clients need different payload size, grouping, or request timing. If both clients can use the same endpoint with minor UI work, keep that endpoint in the shared API.
How do I test the boundary before launch?
Test real screens, not just docs. Check that each client gets only the fields it uses, change one shared rule and confirm both clients follow it, and trace a failed request from client to BFF to backend with one request ID.
When should I skip the BFF pattern?
Skip it when your clients need almost the same data and your real problem sits elsewhere. If one cleaner API solves the issue, that usually gives you less code, fewer moving parts, and less drift over time.