GraphQL Federation

A single GraphQL schema is the natural way to expose an API to clients, and it collides badly with microservices. If Users, Products, and Reviews are owned by three teams, someone has to own the schema — and a monolithic gateway that stitches everything together becomes a bottleneck that every team queues behind, and a service that nobody wants to be on call for.

Federation resolves this by letting each service publish a subgraph — its own GraphQL schema, describing the types it owns — with declarative annotations saying how its types relate to types elsewhere. A router composes those subgraphs into one supergraph, which is what clients see, and at query time it plans and executes the minimum set of subgraph requests needed to satisfy the query.

The key mechanism is the entity: a type with a @key, meaning several subgraphs can contribute fields to the same object. Reviews doesn't need to know anything about how Users stores data; it declares that it can resolve Review.author to a User identified by id, and the router works out the rest.

TL;DR

Quick Example

Three subgraphs composing into one API:

The Review.author resolver is the pattern to internalize: reviews returns { __typename: 'User', id } and nothing else. It has no idea what a User contains, and it doesn't need to — the router sees the reference and fetches the remaining fields from the users subgraph.

Core Concepts

The topology

Each team deploys its subgraph on its own schedule. The supergraph is recomposed when a subgraph schema changes, and composition failures are caught before anything reaches production.

Entities and keys

An entity is a type that can be referenced and resolved across subgraph boundaries. The @key names the fields that uniquely identify it, and any subgraph declaring the same key can contribute fields to it.

This is the conceptual heart of federation. Without entities you have separate APIs behind one endpoint; with them you have one graph where a single object's fields come from wherever they're owned.

Query planning

Step 3 is where federation earns its keep: five reviews by five authors produce one request to users, not five. The _entities field with an array of representations is the federation protocol's batching primitive, and the router uses it automatically.

Ownership directives

@override is the migration tool. Moving a field between subgraphs is normally a breaking coordination problem; @override lets the new owner claim it, with progressive traffic shifting, and the router routes to the new subgraph while the old definition still exists. This is how you refactor a supergraph without downtime.

Composition and schema checks

Schema checks are the practice that makes federation safe at scale. They compare a proposed subgraph change against both the current supergraph (does it still compose?) and recorded client operations from production traffic (does any live query break?). A field removal that no client uses passes; one that a mobile app still queries fails.

Without operation-aware checks, federation gives every team the ability to break every client independently, which is worse than the monolithic gateway it replaced.

The N+1 Problem

Federation makes N+1 a network problem, and it's the dominant performance issue in federated graphs.

Two rules that prevent the majority of federation performance problems: every reference resolver batches, and loaders are per-request. A global DataLoader is a cache that serves one user's data to another.

Alternatives

Implementations: Apollo Router (Rust, the reference implementation), Cosmo (open source, self-hostable, with its own registry), GraphQL Mesh, and Hive Gateway. The Federation specification itself is open, so subgraphs are portable between routers.

The honest guidance: don't federate prematurely. A single schema with a modular resolver layout serves one or two teams perfectly well, and federation adds a router to operate, a composition pipeline, a schema registry, and a new class of cross-subgraph performance problem. Federate when independent team deployment is the actual constraint.

Best Practices

Batch every reference resolver

The single most important practice. The router batches representations into one _entities call; if your handler then issues one query per representation, you've moved N+1 from the client to your database. DataLoader, per request, always.

Run schema checks against real operations in CI

Composition checks catch schema conflicts; operation checks catch client breakage. Both, on every subgraph pull request, comparing against traffic recorded from production. This is what makes independent deploys safe.

Align subgraph boundaries with team and domain boundaries

A subgraph should own a coherent domain that one team owns. Subgraphs split by technical layer, or shared between teams, reproduce exactly the coordination bottleneck federation exists to remove. See Team Topologies and Domain-Driven Design.

Use @override for field migrations

Moving a field between subgraphs without it requires coordinated deploys. With it, the new owner claims the field, traffic shifts progressively, and the old definition is removed afterward. It turns a breaking change into a routine one.

Keep entity keys stable and minimal

Keys are your cross-subgraph contract. Prefer a single stable identifier; compound keys mean every referencing subgraph must carry all of those fields. Changing a key is a breaking change across every subgraph that references the entity.

Set depth, complexity, and rate limits at the router

A federated graph makes it easy for a client to write a query that fans out into dozens of subgraph calls. Query depth limits, cost analysis, and persisted queries (allowing only pre-registered operations) are the controls. See Rate Limiting.

Propagate tracing context through the router

One client query becomes several subgraph requests. Without distributed tracing you cannot tell which subgraph made a query slow. The router should propagate W3C trace context and emit spans per fetch. See Distributed Tracing.

Handle partial failure deliberately

If one subgraph is down, the router can return partial data with errors rather than failing the whole query. Decide per field whether that's acceptable — nullable fields degrade gracefully, non-nullable ones propagate the failure upward and can null out an entire parent object.

Common Mistakes

Unbatched reference resolvers

A global DataLoader

Returning full objects from a cross-subgraph field

Subgraphs split by technical layer

No schema checks

Federating two teams

FAQ

When should we adopt federation?

When multiple teams need to ship schema changes independently and the coordination cost of a shared schema is a real bottleneck — usually around four or more teams contributing to one API. Below that, a single GraphQL service with a well-organized resolver layer gives you the same client experience with none of the router, registry, or composition machinery. Adopting federation for architectural tidiness rather than an actual constraint is the most common way it disappoints.

Is federation slower than a monolithic GraphQL server?

It adds a router hop and cross-subgraph fetches, so a query touching three subgraphs makes at least three internal calls where a monolith would make one process's worth of database queries. The router's parallelization and entity batching keep this modest in practice — typically single-digit milliseconds of routing overhead. The real risk isn't the router; it's unbatched reference resolvers turning one client query into hundreds of database queries.

Federation or schema stitching?

Federation. Stitching was the earlier approach, with composition logic living in the gateway and type relationships configured there — which made the gateway a bottleneck and put cross-service knowledge in the wrong place. Federation inverts it: subgraphs declare their own relationships, and composition is mechanical. Stitching is legacy, and GraphQL Tools' stitching remains available mainly for wrapping APIs you don't control.

Do we have to use Apollo?

No. Federation v2 is an open specification, and there are several independent implementations — Cosmo (open source, self-hostable router and registry), Hive Gateway, and GraphQL Mesh among them. Subgraphs written to the spec are portable. Apollo Router is the reference implementation and is fast (Rust), and its GraphOS registry and schema checks are a commercial product with open alternatives.

How do we handle authorization?

Authenticate at the router and propagate identity to subgraphs via headers, then authorize inside each subgraph, because only the owner knows its own rules. Router-level authorization directives can handle coarse field-level checks. What doesn't work is assuming the router enforces everything — a subgraph must never trust that a request reached it legitimately, since it may be reachable directly.

What about subscriptions?

Supported, and more operationally involved than queries. The router maintains a WebSocket or SSE connection with the client and connects to the subgraph owning the subscription root field; fields resolved from other subgraphs are fetched per event. The practical constraints are connection scaling at the router and the fact that a subscription's payload requiring several subgraphs multiplies per-event fetch cost. See WebSockets and Server-Sent Events.

Related Topics

References