API · Graphql

GraphQL Federation — A Field Guide

GraphQL Federation — A Field Guide is the work that makes the systems talk. The API is the contract between the producer and the consumer; the contract is what determines whether.

John Kihiu12 min read

Federation exists because a single GraphQL schema eventually outgrows a single team. Once you have a catalog service, an orders service, and a reviews service each wanting to expose Product fields, you either force one team to own the whole schema on everyone else's behalf, or you let each team publish its own slice and compose them into one graph at request time. That composition, done properly rather than as a client-side hack, is what Apollo Federation provides.

Subgraphs and schema ownership

A federated graph is made of subgraphs — ordinary GraphQL servers, each with its own schema, resolvers, and deployment lifecycle. The catalog subgraph might own Product's name and price; the reviews subgraph might contribute a reviews field on that same Product type without owning the type itself. No subgraph needs to know about the others' internals. What it needs to agree on is which type it can extend and how to identify an instance of a type it doesn't fully own.

The @key directive and entities

A type that multiple subgraphs need to reference is called an entity, and it's marked with the @key directive naming the field(s) that uniquely identify it:

GRAPHQL · SCHEMA
# Catalog subgraph — owns the base type
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Int!
}

# Reviews subgraph — extends the entity with a new field
type Product @key(fields: "id") {
  id: ID!
  reviews: [Review!]!
}

type Review {
  id: ID!
  body: String!
  rating: Int!
}

In Federation 1 this required an explicit extend type Product plus @external on the borrowed id field. Federation 2 relaxed that: any subgraph can declare the same @key on a type and contribute fields to it without the extend/@external ceremony, as long as the composition step can reconcile the pieces.

Resolving entity references

When the gateway needs to attach reviews to a product that the catalog subgraph already resolved, it doesn't refetch the whole object — it asks the reviews subgraph to resolve its fields given just the entity's key. Each subgraph that contributes to an entity it doesn't own implements a reference resolver, commonly named __resolveReference:

JAVASCRIPT · RESOLVER
const resolvers = {
  Product: {
    __resolveReference(reference, context) {
      // reference = { __typename: "Product", id: "42" }
      return { id: reference.id };
    },
    reviews(product, args, context) {
      return context.reviewsLoader.loadForProduct(product.id);
    },
  },
};

The gateway supplies a "representation" — essentially just the __typename and key fields — and the subgraph's job is to turn that representation into whatever it needs to resolve its own fields. This is the mechanism that lets a query touching five subgraphs execute as a small number of targeted requests instead of the gateway guessing at REST endpoints.

The gateway, composition, and query planning

The gateway (or router, in Apollo's newer Rust-based implementation) doesn't proxy requests blindly. At startup — or, in managed federation, on every schema publish — it fetches each subgraph's schema and runs composition: merging the subgraph schemas into one supergraph schema, validating that @key fields line up and that no two subgraphs define the same field on a type in a conflicting way. Composition failures are caught in CI, before a bad subgraph deploy reaches production.

At request time, the gateway builds a query plan: given the incoming operation, it decides which subgraphs must be called, in what order, and how to stitch the responses back into the shape the client asked for. A query that spans Product, reviews, and inventory might plan out as one call to catalog, followed by parallel calls to reviews and inventory using the product IDs returned from the first call.

Composition is a build-time gate, not a runtime surprise

Most federation setups run rover subgraph check (or the equivalent) in CI against the current supergraph before a subgraph schema change merges. That's what catches a renamed field or a conflicting @key before it breaks query planning for everyone else's queries.

How this differs from schema stitching

Schema stitching — the older approach — merges independently-designed schemas at the gateway using ad hoc delegation logic, usually written by whoever owns the gateway. There's no formal concept of entity ownership: if two schemas both define a Product type, reconciling them is manual, gateway-side code, and it tends to become a bottleneck the gateway team has to maintain for every other team's types. Federation inverts that: ownership of a type's fields is declared by each subgraph via @key and field placement, composition is a mechanical, checkable step rather than hand-written glue, and query planning is generated rather than hardcoded. The trade-off is that federation requires everyone to adopt its directives and constraints, whereas stitching can bolt together schemas that were never designed to be composed.

AspectSchema stitchingApollo Federation
Entity ownershipImplicit, resolved by gateway codeExplicit via @key
CompositionManual merge logicAutomated, checked in CI
Cross-service fieldsCustom delegation resolversReference resolvers per subgraph
Query executionGateway-authoredGenerated query plan

Federation isn't free — you're running a gateway, a composition step, and a contract between subgraph teams that has to hold under change. It earns its cost when several teams genuinely need to contribute fields to the same conceptual entities. If only one team owns the whole domain, a single non-federated GraphQL server is simpler and does the same job.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.