Apollo Federation for GraphQL Microservices
Learn how Apollo Federation composes multiple GraphQL subgraphs into a single unified API gateway for microservice architectures.
What you'll learn
- ✓How Apollo Federation splits a GraphQL schema across microservices
- ✓How to define entities and reference resolvers for cross-service relationships
- ✓How the Apollo Router composes subgraphs into a unified supergraph
Prerequisites
- •Basic GraphQL knowledge
- •Understanding of microservice architecture
The Problem with Monolithic Schemas
As your GraphQL API grows, a single monolithic schema becomes a bottleneck. Multiple teams editing the same schema file causes merge conflicts, deployment coupling, and unclear ownership. If the Users team and the Orders team both need to modify the same server, every deployment carries risk for both teams.
Apollo Federation solves this by letting each team own their own GraphQL service (called a subgraph) while presenting a single, unified API to clients through a gateway (the Apollo Router).
How Federation Works
Federation has three core concepts:
- Subgraphs are independent GraphQL services, each owning a portion of the overall schema.
- The supergraph schema is the composed result of all subgraph schemas merged together.
- The Apollo Router sits in front of all subgraphs, receives client queries, breaks them into sub-queries, sends each piece to the appropriate subgraph, and assembles the final response.
Setting Up a Subgraph
Install the federation package:
npm install @apollo/server @apollo/subgraph graphql
A Users subgraph defines the User entity:
import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { startStandaloneServer } from '@apollo/server/standalone';
import gql from 'graphql-tag';
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key"])
type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
`;
const resolvers = {
Query: {
me: (_, __, context) => {
return db.users.findById(context.userId);
},
},
User: {
__resolveReference: (reference) => {
// Called when another subgraph references a User by ID
return db.users.findById(reference.id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
const { url } = await startStandaloneServer(server, { listen: { port: 4001 } });
console.log(`Users subgraph running at ${url}`);
The @key Directive
The @key directive marks a type as an entity that can be referenced across subgraphs. The fields argument specifies which fields uniquely identify this entity:
type User @key(fields: "id") {
id: ID!
name: String!
}
You can define composite keys when a single field is not sufficient:
type Product @key(fields: "upc sku") {
upc: String!
sku: String!
name: String!
}
Extending Entities Across Subgraphs
The power of federation is that one subgraph can extend an entity owned by another. An Orders subgraph can add an orders field to the User type:
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@external"])
type Query {
order(id: ID!): Order
}
type Order @key(fields: "id") {
id: ID!
product: String!
quantity: Int!
user: User!
}
type User @key(fields: "id") {
id: ID!
orders: [Order!]!
}
`;
const resolvers = {
Query: {
order: (_, { id }) => db.orders.findById(id),
},
User: {
orders: (user) => {
// The router provides the user.id from the Users subgraph
return db.orders.findByUserId(user.id);
},
},
Order: {
__resolveReference: (ref) => db.orders.findById(ref.id),
},
};
When a client queries { me { name orders { product } } }, the router fetches name from the Users subgraph and orders from the Orders subgraph, then combines the results.
Reference Resolvers
The __resolveReference resolver is how federation fetches entity data. When the Orders subgraph returns an Order with a user field containing just { id: "123" }, the router calls the Users subgraph’s __resolveReference with that reference to get the full User object.
User: {
__resolveReference: async (reference) => {
// reference = { id: "123" }
return db.users.findById(reference.id);
},
},
Setting Up the Apollo Router
The Apollo Router is a high-performance Rust binary that replaces the older Node.js gateway. Download it and create a supergraph configuration:
# supergraph-config.yaml
federation_version: =2.0.0
subgraphs:
users:
routing_url: http://localhost:4001
schema:
subgraph_url: http://localhost:4001
orders:
routing_url: http://localhost:4002
schema:
subgraph_url: http://localhost:4002
Compose the supergraph schema and start the router:
# Install the Rover CLI
npm install -g @apollo/rover
# Compose the supergraph schema
rover supergraph compose --config supergraph-config.yaml > supergraph.graphql
# Start the router
./router --supergraph supergraph.graphql
The router now accepts queries on port 4000 and routes them to the appropriate subgraphs.
Query Planning
When the router receives a query, it creates a query plan that determines which subgraphs to call and in what order. For independent fields, it parallelizes requests. For dependent fields (like fetching a user, then their orders), it sequences them.
You can inspect query plans for debugging:
query GetUserOrders {
me {
name # Fetched from Users subgraph
email # Fetched from Users subgraph
orders { # Fetched from Orders subgraph (requires User.id first)
product
quantity
}
}
}
The router sends one request to the Users subgraph for name, email, and id, then uses the id to request orders from the Orders subgraph.
Shared Types with @shareable
When multiple subgraphs need to resolve the same field on a type, use the @shareable directive:
# In both subgraphs
type Product @key(fields: "id") {
id: ID!
name: String! @shareable
}
Without @shareable, composition fails because the router does not know which subgraph should resolve name.
Migrating to Federation Incrementally
You do not need to break your monolith into microservices all at once. Start by wrapping your existing monolith as a single subgraph:
- Add
@apollo/subgraphand mark your types with@key. - Set up the router pointing to your monolith as the only subgraph.
- Extract one service at a time. Move the
Orderstype to a new subgraph while extendingUserfrom the original. - Repeat until each domain has its own subgraph.
This incremental approach lets you validate federation works with your existing schema before committing to a full microservice split.
Common Pitfalls
Circular dependencies: Subgraph A extends a type from Subgraph B, and B extends a type from A. This works in federation but makes query plans complex. Keep dependencies unidirectional when possible.
Over-fetching in reference resolvers: If __resolveReference always fetches the full entity from the database, you may load data the client did not request. Use the info parameter or field-level resolvers for expensive fields.
Missing entities: If a subgraph references an entity that does not exist in the owning subgraph, the router returns null. Always handle this case gracefully.
Summary
Apollo Federation lets teams independently develop and deploy GraphQL subgraphs while presenting a unified API to clients. Each subgraph owns its domain types, entities can be extended across subgraphs using @key and reference resolvers, and the Apollo Router handles query planning and composition. Start by federating your existing monolith as a single subgraph, then incrementally extract services as your team structure demands.
Related articles
- GraphQL GraphQL Federation: A Practical Overview
Understand Apollo Federation: subgraphs, the gateway, entity references, and when to choose federation over a monolithic GraphQL schema.
- GraphQL GraphQL Caching with Apollo Client
How Apollo Client's normalized cache works, why entity IDs matter, and the patterns for cache updates, refetches, and consistent UI after mutations.
- GraphQL GraphQL Subscriptions Tutorial
Add real-time data to your GraphQL API with subscriptions. Learn the transport, pubsub patterns, and a working Apollo example.
- GraphQL Authentication and Authorization in GraphQL
Implement auth in GraphQL using context-based authentication, custom directives, and field-level authorization patterns.