Your API Was Designed for Servers, Not Clients
Most APIs are designed from the data model out. Clients need action-oriented, client-shaped responses. N+1 and over-fetching aren't frontend failures.
The N+1 problem gets diagnosed as a frontend failure. The iOS team is making too many requests. The React client is fetching data inefficiently. The mobile app has chatty behavior that needs to be fixed on the client side.
This diagnosis is almost always wrong. The N+1 problem, chronic over-fetching, and chatty client behavior are symptoms of API design that was done from the server's perspective. The client is doing its best with an API that wasn't designed to serve it.
How server-centric API design happens
When a backend team designs an API, they typically start with the data model. There are users, there are posts, there are comments. The API exposes these resources: GET /users/:id, GET /posts/:id, GET /posts/:id/comments. The design is clean, REST-compliant, and maps neatly onto the database schema.
This feels like good design. It follows conventions. It is easy to document. Each endpoint has a clear scope and a consistent return type. The backend team ships it and moves on.
The frontend team receives the API and builds a page that displays a feed of posts with the author name and comment count for each. To render this page, the client needs to: fetch the list of posts, then for each post, fetch the author details, then fetch the comment count. For a feed of twenty posts, that is forty-one HTTP requests: one for the list, twenty for authors, twenty for comment counts.
This is the N+1 problem. It exists not because the frontend team made poor choices but because the API was designed to model the data, not to serve the client's use cases. The clean, resource-oriented design produces an integration that is functionally broken under real conditions.
Over-fetching is the other side of the same coin
The N+1 problem is about making too many requests. Over-fetching is about each request returning too much data. Both stem from the same root cause: the API returns what it has, not what is needed.
Consider a mobile application displaying a list of users. The API returns the full user object: id, name, email, phone, address, preferences, account settings, profile metadata. The mobile list view needs id, name, and avatar URL. The client downloads the full object because that is what the API provides; it discards everything except three fields.
This is not a trivial inefficiency. On mobile networks, payload size directly affects load time. The over-fetching client is slower not because of a network problem but because the API design creates unnecessary data transfer. The overhead compounds: list views typically display many items, and if each item requires a full object fetch, you are multiplying the waste.
The backend team looks at this and sees a client that is fetching more than it needs. The client team looks at this and sees an API that doesn't support selective field retrieval. Both observations are correct. The root cause is that nobody designed the API from the client's perspective when the client was known.
Why REST conventions aren't enough
REST is a great set of conventions for resource modeling. It is incomplete as a guide for API design when you have specific clients with specific needs.
The core tension: REST treats the API as a generic interface over resources. A generic interface maximizes flexibility and serves any client equally. But most APIs don't serve any client — they serve specific clients with specific use cases. An API that serves a mobile app, a web frontend, and third-party integrations has three distinct sets of performance requirements and data shape requirements. A generic interface optimizes for none of them.
This is why GraphQL got traction: not because REST is bad, but because GraphQL makes it explicit that clients should specify what they need and the server should deliver exactly that. The client describes its data requirements; the server compiles those requirements into efficient data fetching. The N+1 problem still exists in naive GraphQL implementations, but the architecture pushes you toward solving it at the API layer via data loaders and batching, rather than at the client layer via request consolidation.
The Backend for Frontend pattern
The Backend for Frontend (BFF) pattern is the pragmatic response to this problem for teams that can't replace their existing APIs. The idea: for each distinct client type — mobile app, web frontend, third-party API — build a thin API layer that is shaped specifically for that client's needs.
The BFF aggregates calls to underlying services, does the joins that would otherwise produce N+1 queries on the client, shapes the response to exactly what the client needs, and handles client-specific concerns like authentication token formats and error message localization. The underlying services remain generic and resource-oriented. The BFF makes them accessible to a specific client efficiently.
The objection to BFF is that it multiplies API code and fragments responsibility. These concerns are real. A BFF for the web client and a separate BFF for the mobile client means two teams or at least two codebases to maintain. If the product changes, both BFFs need to change. This is genuine overhead.
The response is that you're already paying this cost, just invisibly. The "chatty client" problem, the over-fetching problem, the N+1 problem — these all have performance and reliability costs that manifest as slower pages, worse mobile experience, and over-loaded backend services. The BFF pattern makes the client-serving layer explicit rather than leaving it as an emergent property of whatever the client can hack together from a generic API.
What good API design for clients looks like
The practical question is not "REST vs GraphQL vs BFF" but "did we design this API thinking about how clients will use it?"
That means the team building the API should know what the main client use cases are before designing the endpoints. Not every possible use case — that leads to premature abstraction. The ten most important pages or flows. For each, what data does the client need? What shape should that data be? How often will the client need to request it?
An API designed with this approach will have endpoints like GET /feed that returns posts with embedded author summaries and comment counts, ready for display, rather than three separate resource endpoints that the client must combine. It will support field selection or at least have specific response shapes for the known use cases. It will batch what clients typically need together.
This is not about abandoning resource orientation. It's about adding client orientation as a second design constraint. Resources define the vocabulary. Use cases define the grammar. An API that has only the former makes clients speak in telegrams when they need to have conversations.
The N+1 problem will not be fixed by better mobile engineers or more disciplined frontend developers. It will be fixed by backend teams who design APIs the way the client actually needs them, not the way the database wants to expose them.
Work with me
I consult with engineering teams on AI adoption, cloud architecture, and engineering effectiveness. If this post surfaced a challenge you're facing, let's talk.
Get in touch →Related posts
Explore more on these topics: