Federation

This section documents the current GraphQL specification (October 2021), plus the working draft for @defer/@stream/@oneOf, and the GraphQL-over-HTTP specification, as published at graphql.org and spec.graphql.org — which are the references these pages are written and verified against — and, for the integration pages, against Spring for GraphQL (2.0.x), Strawberry, Ariadne, and the client docs for Apollo Client / urql / Relay. No specific patch version is pinned. Some surfaces (Apollo Router/managed federation, graphql-ws internals, the Relay compiler internals, GraalVM native) are linked rather than documented in depth.

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.

flowchart LR C[Client] --> R[Router] R --> S1[Products subgraph] R --> S2[Orders subgraph] R --> S3[Reviews subgraph] S1 -. entity key .-> R S2 -. entity key .-> R S3 -. entity key .-> R R --> C

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 @key — the simplest option, and the right default until more than one team (not just more than one module) needs to deploy its part of the schema independently.

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 @key-based entity model. The Guild’s schema-stitching documentation maintains the current tooling for it; federation has since become the more common choice for new distributed graphs because entity resolution is declared in the subgraph’s own schema instead of centralized gateway code.

Federation

Several independently owned and deployed subgraphs, composed by a router, with entities declared via @key so subgraphs contribute fields to types they don’t fully own. The right choice once independent team ownership and independent deployability are the actual goal, not just code organization.

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 control covers those limits for a single schema, and the same reasoning applies at the supergraph level.

  • 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.