Federation
|
This section documents the current GraphQL specification (October 2021), plus the working draft for
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as GraphQL tooling iterates quickly. |
Federation composes several independently deployed GraphQL services into one unified schema that a client
queries as if it were a single API. This page covers why that composition is useful, the entity and @key
building blocks that make it work, the router that plans queries across services, and how federation compares
to the alternatives.
The problem federation solves
A single GraphQL schema serves most APIs well for a long time, but as an organization grows past one team owning the whole graph, a monolithic schema starts to strain in the same ways a monolithic REST API does: every team’s changes go through the same deploy, a bug in one team’s resolver can take down fields no one on that team owns, and scaling the service means scaling all of it together even though only one team’s traffic actually grew.
Federation addresses this by letting each team own a subgraph — a normal, independently deployable GraphQL service that only knows about its own slice of the domain — while a router (also called a gateway) composes every subgraph’s schema into one supergraph that clients query through a single endpoint, unaware that the response was assembled from several services underneath. A products team, an orders team, and a reviews team can each own, deploy, and scale their own subgraph independently, yet a client can still ask for a product, its reviews, and the order history that references it in one query.
graphql.org/learn/federation is the canonical introduction to this
model and the source for the terminology used throughout this page; the worked examples below follow the same
shape.
Subgraphs and the supergraph
Each subgraph declares only the types and fields it owns. A products subgraph might declare:
type Product @key(fields: "id") {
id: ID!
title: String!
price: Float!
inStock: Boolean!
}
type Query {
product(id: ID!): Product
}
while an independently deployed reviews subgraph declares its own piece of the graph, including a stub of the
Product type it doesn’t own so it can attach a field to it:
type Review {
id: ID!
body: String!
rating: Int!
product: Product!
}
extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
}
type Query {
reviewsForProduct(productId: ID!): [Review!]!
}
Neither subgraph knows the other exists. The router composes both schemas at startup (or ahead of time, via a schema registry) into a single supergraph a client queries without ever seeing the seam:
query ProductWithReviews($id: ID!) {
product(id: $id) {
title
price
reviews {
body
rating
}
}
}
Answering that query means the router calls the products subgraph for title/price, then calls the reviews
subgraph for reviews, passing along the id the products subgraph already returned — the mechanism covered
next.
Entities and the @key directive
An entity is a type that more than one subgraph contributes fields to, and @key is what makes that possible — it declares which field (or fields) uniquely identify an instance of the type across subgraph boundaries, so
the router knows what value to hand a subgraph when asking it to resolve its fields for an object it didn’t
originally fetch. In the example above, @key(fields: "id") on both the owning Product type and the reviews
subgraph’s extend-ed stub is what lets the router say, in effect, "here is a product with id: "42", add your
reviews field to it."
A key can also span multiple fields (@key(fields: "sku manufacturerId")) when no single field uniquely
identifies an instance, and a type can declare more than one @key if different subgraphs each need a
different field to look it up by. Apollo’s
federated directives reference documents @key alongside the other directives (@external, @requires,
@provides, @shareable) that govern how fields move between subgraphs, and
Apollo’s entities guide walks through the same
Product/Review shape used above in full.
Reference resolvers
Once a subgraph declares @key on a type it doesn’t fully own, it needs a way to turn the key value the router
hands it back into a real object — that function is a reference resolver. In the reference JavaScript
implementation it is a special __resolveReference resolver registered on the entity’s type, called with the
partial object the router assembled from the @key fields alone:
const resolvers = {
Product: {
// Called by the router with only the @key fields populated: { id: "42" }.
__resolveReference(productRef, context) {
return context.db.findProductById(productRef.id);
},
reviews(product, args, context) {
return context.db.findReviewsByProductId(product.id);
},
},
};
The router calls resolveReference exactly when it needs to attach fields owned by this subgraph to an
entity it first encountered somewhere else — for the query above, once the products subgraph has already
returned \{ id: "42", title: …, price: … }, the router calls the reviews subgraph’s resolveReference
with \{ id: "42" } to fetch the object it then reads reviews off of. A reference resolver that is called once
per entity in a list is exactly the N+1 shape covered in
Performance & N+1, and the same batching/DataLoader fix
applies — most server frameworks also support a batch reference resolver that receives the whole list of
keys from one query at once instead of one call per entity.
Apollo’s entity-resolving guide
documents both the per-entity and batched resolver shapes.
The router and query planning
The router is the single component clients actually talk to. On startup (or when a new subgraph schema is
published to a registry) it composes every subgraph’s schema into the supergraph, validating that @key
fields, type shapes, and directive usage agree across subgraphs before ever serving a query — composition
failures are caught here, not at request time. For every incoming query, the router then builds a query
plan: a sequence of sub-requests to the individual subgraphs (some of them dependent on an earlier one’s
result, as with the reviews field above) plus the logic to stitch each subgraph’s partial response back into
one shape.
Composition and query planning are exactly the parts of federation with the most implementation-specific
behavior and tooling — Apollo’s own router (a managed or self-hosted binary), schema registries, and the
composition algorithm’s edge cases are deliberately linked rather than documented in depth here:
Apollo’s router documentation covers the
router itself, and Apollo Federation’s own documentation
covers composition end to end.
Federation vs. schema stitching vs. a modular single schema
Composing multiple GraphQL schemas into one is not unique to federation — it is worth knowing the alternatives
before committing to it, since federation’s operational cost (a router, a composition step, cross-subgraph
@key discipline) is only worth paying once a single schema genuinely strains under more than one team:
| Approach | When it fits |
|---|---|
A modular single schema |
One deployable service, but organized into modules/packages by domain inside
the codebase. No router, no composition step, no cross-service |
Schema stitching |
An older, more manual technique for combining separately defined schemas at the gateway
layer by hand-written merge/delegation logic, predating federation’s |
Federation |
Several independently owned and deployed subgraphs, composed by a router, with entities
declared via |
The ecosystem is also actively working to standardize federation itself rather than leave it as a single
vendor’s implementation detail: the GraphQL Foundation’s Composite Schemas Working Group — with engineers
from Apollo, ChilliCream, Graphile, Hasura, Netflix, The Guild, and WunderGraph participating — is developing
an open specification for composing subgraphs, published at
the Composite Schemas spec repository. That effort is
linked rather than documented in depth here, since it is still evolving and Apollo Federation remains the
implementation this page’s examples follow.
Practical considerations
A few points matter in practice once a graph is actually split across subgraphs, beyond getting composition to succeed:
-
Ownership boundaries should follow team boundaries, not convenience. An entity split across three subgraphs "because the fields happened to be there" recreates the tight coupling federation is meant to remove — see `Schema Design’s section on domain-shaped schemas for the same principle applied within a single schema.
-
Local development gets one extra moving part: the router itself, composing whichever subgraphs are running locally. Most router implementations support composing against local subgraph URLs so a single team can iterate without standing up every other team’s service.
-
Errors still follow the same envelope. A subgraph failing to resolve an entity produces a normal GraphQL error the router folds into the overall response — `Response & error handling’s partial-success model applies across subgraph boundaries exactly as it does within one service.
-
Authorization still belongs in the domain/service layer of the owning subgraph, not the router — the router’s job is composition and query planning, not enforcing who may see which field; see
Authorization. -
Demand-control limits (depth, complexity, rate limiting) apply to the router’s view of the supergraph, and a query that looks shallow against one subgraph can still fan out into many subgraph calls once the router plans it —
Security & demand controlcovers those limits for a single schema, and the same reasoning applies at the supergraph level.
Related pages
-
Schema Design— naming, nullability, and governance conventions that matter even more once several teams contribute to one graph. -
Performance & N+1— the batching pattern a reference resolver called once per entity needs in exactly the same way a regular resolver does. -
Authorization— where authorization checks belong, which does not change when a field’s owning subgraph does. -
Security & demand control— depth, complexity, and rate limits applied to a router’s view of the supergraph. -
Response & error handling— the error envelope a subgraph’s failure still has to fit into once the router assembles the final response.