GraphQL Federation allows you to build a single, unified data graph by combining multiple backend services (subgraphs). The gateway intelligently plans and executes queries across these services. This guide covers the core directives and concepts that make it work.
We'll use a simple e-commerce example with three subgraphs:
- Users Service: Manages user data (
id,name). - Products Service: Manages product data (
upc,price,name). - Reviews Service: Manages reviews, which are linked to users and products.
These are the absolute essentials for making federation work.
- What it is: A query field automatically added to your subgraph schema that returns its Schema Definition Language (SDL).
- Why you use it: It's the "heartbeat" of federation. The gateway polls this field on each subgraph to fetch its schema and capabilities, which it then composes into the final supergraph.
- Example Usage:
You don't add this to your schema yourself; the federation library does it. You can query it to verify a service is running correctly.
# Query sent by the gateway query { _service { sdl } }
- What it is: A directive that designates a field (or set of fields) as the unique primary key for an entity.
- Why you use it: It tells the gateway, "This object type is an entity, and you can use this key to look it up." This allows other services to reference and extend the entity.
- Example (in the
usersservice):This tells the gateway that any other subgraph can get a# users-subgraph/schema.graphql type User @key(fields: "id") { id: ID! name: String! }
Userif it knows theid.
- What it is: A
@keydirective that uses a combination of multiple fields to form a unique primary key. - Why you use it: Use this when a single field isn't enough to uniquely identify an object.
- Example (in the
productsservice): A product might be unique only by its UPC within a specifictenantId.# products-subgraph/schema.graphql type Product @key(fields: "upc tenantId") { upc: ID! tenantId: ID! name: String }
- What it is: The ability to define multiple, alternative keys for a single entity.
- Why you use it: Allows an entity to be fetched using different unique identifiers. For example, a product could be found by its
upcOR itssku. - Example (in the
productsservice):# products-subgraph/schema.graphql type Product @key(fields: "upc") @key(fields: "sku") { upc: ID! sku: String! name: String }
These directives were introduced in Apollo Federation 2 to provide more power and flexibility. You must use @link to enable them.
- What it is: A directive that links your subgraph's schema to the official Federation specification, enabling Federation 2 features.
- Why you use it: It's the entry point for all Federation 2 features like
@shareable,@override,@interfaceObject, etc. - Example (at the top of your schema):
# Add this to the top of every subgraph schema extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@requires", "@provides", "@shareable", "@override", ...]) type Product @key(fields: "upc") { # ... }
These directives manage how data and types are shared, extended, and resolved across subgraphs.
- What it is: A directive on a computed field that indicates it needs other external fields to be resolved.
- Why you use it: To calculate a field in one service using data owned by another service. The gateway ensures the required fields are fetched first.
- Example: The
usersservice storesnameandemail. Thereviewsservice wants to compute areviewerHandlelike"Jane D. ([email protected])".The gateway will first fetch# reviews-subgraph/schema.graphql extend type User @key(fields: "id") { id: ID! @external name: String @external # from the users service email: String @external # from the users service # This field is computed here, but needs data from the users service reviewerHandle: String! @requires(fields: "name email") }
nameandemailfrom theusersservice before calling thereviewerHandleresolver in thereviewsservice.
- What it is: A directive on a field that returns an entity, indicating that its resolver will also provide other fields for that entity.
- Why you use it: To optimize queries by fetching related data in a single resolver trip instead of letting the gateway make a second network call.
- Example: The
reviewsservice owns reviews. AReviewhas anauthor(aUser). Thereviewsservice can probably fetch the user'snameat the same time it fetches the review, saving a hop to theusersservice.# reviews-subgraph/schema.graphql type Review @key(fields: "id") { id: ID! body: String! # When you resolve 'author', you also provide the 'name' author: User! @provides(fields: "name") } extend type User @key(fields: "id") { id: ID! @external name: String @external }
- What it is: Marks a field or object type definition as being defined and resolved identically across multiple subgraphs.
- Why you use it: To prevent composition errors when multiple subgraphs define the same simple, non-entity type (like a
Timestampobject) or the same field on a shared entity. It tells the gateway, "Don't worry, these are the same thing." - Example: Both
productsandreviewsservices want to use aTimestamptype.# In both products-subgraph and reviews-subgraph type Timestamp @shareable { iso8601: String! unix: Int! } type Product @key(fields: "upc") { upc: ID! createdAt: Timestamp @shareable # Field is also shareable }
- What it is: Allows a subgraph to take over the responsibility (ownership) of a field that was originally defined in another subgraph.
- Why you use it: Essential for migrating fields between services without downtime. You can deploy a new service that
@overrides a field, and the gateway will start routing requests for that field to the new service. - Example: The
productsservice originally ownedname. A newmarketingservice will now be the source of truth for product names.# marketing-subgraph/schema.graphql extend type Product @key(fields: "upc") { upc: ID! @external # I am now the owner of the 'name' field, overriding the 'products' service name: String @override(from: "products") }
- What it is: Hides a field or type from the final supergraph schema, making it invisible to clients but still available for use within its own subgraph (e.g., for
@requires). - Why you use it: To hide implementation details or internal-only fields that other services might need for computation but clients should not see.
- Example: The
productsservice has an internal ID that thereviewsservice needs, but it shouldn't be public.# products-subgraph/schema.graphql type Product @key(fields: "upc") { upc: ID! # This ID is for internal use only and won't be in the supergraph internalDatabaseId: ID! @inaccessible }
This is not a directive but an operational feature. When enabled, the gateway includes detailed performance and query plan information in the extensions of a GraphQL response. This allows tools like Apollo Studio to visualize exactly how a query was executed across all your subgraphs, helping you debug performance bottlenecks.
| Directive / Concept | Purpose | Federation Version |
|---|---|---|
_service |
The "heartbeat" query for the gateway to fetch a subgraph's schema (SDL). | v1+ |
@key |
Declares an object type as an "entity" with a primary key. The foundation of federation. | v1+ |
@requires |
Specifies that a field's resolver needs external fields from another service. | v1+ |
@provides |
Optimizes queries by resolving fields from another entity in the same service call. | v1+ |
@link |
Links to the Federation spec to enable v2 features. Required for all below. | v2+ |
@shareable |
Prevents composition errors by marking a field/type as identical across subgraphs. | v2+ |
@override |
Allows a subgraph to take ownership of a field from another subgraph (for migration). | v2+ |
@inaccessible |
Hides a field from the public supergraph but keeps it for internal use. | v2+ |
@tag |
Adds metadata labels to parts of your schema for external tools (e.g., contracts). | v2+ |
@interfaceObject |
Allows a subgraph to implement an entity interface that it doesn't own. | v2+ |
@composeDirective |
Allows you to apply a custom directive in a subgraph and have it composed into the supergraph. | v2+ |
federated tracing |
An operational feature (not a directive) for deep performance monitoring. | v1+ |