API Composition
A pattern where a composer service queries multiple microservices and joins the results in memory. The simplest way to implement cross-service queries.
What is API Composition?
In short
API composition is a microservices pattern where a single composer service calls several downstream services in parallel, joins their responses in memory, and returns one combined result to the client. It is the simplest way to answer a query whose data is spread across multiple services that each own their own database.
What API composition actually is
In a monolith, a single SQL JOIN pulls together a user, their orders, and their billing status because all that data lives in one database. In microservices, each service owns its own private database, so that JOIN no longer exists. The data for one screen might live in five services.
API composition solves this by putting a composer in front of those services. The composer receives one request, calls each service it needs, waits for the responses, stitches them together in application code, and returns a single payload. The client makes one call instead of five.
The composer can be an API gateway, a Backend for Frontend, a dedicated aggregation service, or a GraphQL server resolving fields from different sources. Whatever the form, the defining trait is the same: the join happens in memory at the API layer, not in any database.
How it works under the hood
The composer fans out. For a profile page it might call the User Service, the Order Service, and the Billing Service. The important detail is to fire these calls in parallel, not one after another. If each call takes 100ms, sequential calls cost 300ms while parallel calls cost about 100ms, the time of the slowest one.
Once the responses arrive, the composer maps each one onto a field of the combined object and serializes the result. It holds no database of its own and stores no state between requests, which is what lets you run several copies behind a load balancer and scale them freely.
Every outgoing call needs a timeout and a circuit breaker. A common rule is to set the composer's overall deadline to 60 to 70 percent of the client's timeout. That leaves room for serialization and network transit so the client does not give up before the composer can reply.
Two failure modes dominate. A partial failure is when one downstream is slow or down; for a dashboard the composer should return the data it has and mark the missing piece, since a page with two of three panels beats a 500 error. An N+1 amplification is when the composer fetches a list of 20 items and then makes 20 follow-up calls; the fix is a batch API such as GET /users?ids=1,2,3 or a batching layer like DataLoader.
When to use it and the trade-offs
Reach for API composition when a single view needs data from multiple services and you want to cut client round trips, especially on mobile or high-latency networks. It also hides your backend topology from clients, so you can split or merge services without changing the app.
The big trade-off is consistency. Each service answers at a slightly different instant, so the order data and the billing data may be a few hundred milliseconds out of sync. For most UIs that staleness is invisible. If you need strict consistency across the joined data, composition is the wrong tool and you want a saga or a distributed transaction instead.
Skip composition when the data comes from a single service, in which case you just proxy the call, and avoid it when the call graph is so deep that service A calls B calls C calls D. Deep fan-out turns one slow leaf into a slow everything. Flatten the architecture before composing.
The pattern's main competitor is CQRS with a read model, where events from each service are denormalized into one query-optimized store ahead of time. Composition is cheaper to build and always fresh but pays the fan-out cost on every request; a precomputed read model is fast to read but adds eventual consistency and pipeline machinery.
A concrete example
A retail app renders an order detail screen showing the customer, the line items, the shipment status, and a recommended add-on. Those four pieces live in the Customer, Order, Shipping, and Recommendation services.
An order-detail composer receives GET /order/8842, fires all four calls at once with a 400ms deadline, and assembles the result. Customer and Order answer in 60ms, Shipping in 120ms, and Recommendation is the straggler at 380ms. The composer returns at about 380ms, the slowest call plus a few milliseconds of merge.
One day Recommendation is overloaded and blows past the deadline. The composer ships the customer, order, and shipping data with recommendation set to null and a flag of SERVICE_UNAVAILABLE. The app renders the full order and simply hides the suggestions strip. The user still completes their task.
Where it is used in production
Netflix
The Zuul gateway and edge layer compose responses from over 100 backend services into single payloads tailored per device.
Spotify
Its API gateway aggregates data from hundreds of microservices so a client screen makes one request instead of many.
Uber
Query and gateway layers compose ride, driver, and payment data from separate services for each app screen.
GraphQL servers
Apollo and similar servers compose data by resolving each field from its own service, batching with DataLoader to avoid N+1 calls.
Frequently asked questions
- What is the difference between API composition and CQRS?
- API composition joins data from several services at read time, on every request, so it is always fresh but pays the fan-out latency each call. CQRS precomputes a denormalized read model from service events, so reads are fast but the data is eventually consistent and you have to build and run the pipeline. Use composition for simplicity and freshness, CQRS when read volume is high and you can tolerate slight staleness.
- Should I make the downstream calls in parallel or sequentially?
- In parallel, almost always. If three services each take 100ms, sequential calls cost 300ms while parallel calls cost about 100ms, the time of the slowest one. Only go sequential when one call genuinely depends on the result of another, for example when you need the user ID from the first response to query the second.
- What happens when one of the services is down?
- For read-heavy views, return the data you do have and mark the missing section, such as setting billing to null with an error flag, so the client can show a placeholder. A dashboard with most of its panels is more useful than an error page. For transactional flows where every piece is required, fail fast instead.
- Where does the composer live?
- It can be an API gateway, a Backend for Frontend dedicated to one client type, a standalone aggregation service owned by the team that owns the endpoint, or a GraphQL server. Keep it stateless with no database of its own so you can run several instances behind a load balancer.
- What is the N+1 problem in API composition?
- It happens when the composer fetches a list of N items and then makes one extra call per item to enrich it, producing N+1 round trips. Fix it with a batch endpoint like GET /users?ids=1,2,3 or a batching layer such as DataLoader that collapses many per-item lookups into one call.
Learn API Composition hands-on
This page explains the idea. The full lesson lets you step through the ring as servers join and leave, read the implementation, and check yourself with a quiz. It is one of 760+ lessons in the System Design Masterclass, from your first API call to distributed consensus. Eleven Foundation lessons are free, no signup. Lifetime access is ₹499 in India or $7.99 worldwide, one payment, no subscription.
Related lessons
Lessons that touch on API Composition as part of a larger topic.
See also
Related glossary terms you might want to look up next.
API Gateway
A single entry point for all client requests that routes them to the appropriate microservice. Handles auth, rate limiting, and request transformation.
Microservices
An architecture where an application is split into small, independent services that communicate over the network. Each service owns its own data and can be deployed separately.
CQRS
Command Query Responsibility Segregation: using different models for reading and writing data. Reads and writes have different performance needs, so separate them.
Monolith
A single, unified application where all features share the same codebase and deployment. Simpler to start with but harder to scale individual parts.
Service Discovery
The mechanism by which microservices find and communicate with each other. Services register themselves and others can look them up by name.
Circuit Breaker
A pattern that stops calling a failing service after repeated failures, preventing cascade failures. Like an electrical circuit breaker that cuts power to prevent fires.