Apollo Federation enables you to compose multiple GraphQL services into a single unified graph. It's the standard for building GraphQL APIs with microservices architecture.
What is Federation?
Federation allows you to:
- Split GraphQL schema across services
- Extend types from other services
- Reference entities across services
- Maintain service boundaries
Monolith vs Federation
Monolith GraphQL:
┌─────────────────────┐
│ GraphQL API │
│ ┌───────────────┐ │
│ │ All Types │ │
│ └───────────────┘ │
└─────────────────────┘Federated GraphQL:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Users │ │ Products │ │ Orders │
│ Service │ │ Service │ │ Service │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
└────────┬───────┴────────┬───────┘
│ │
┌──────▼────────────────▼──────┐
│ Apollo Gateway │
└─────────────────────────────┘Setting Up Federated Services
User Service
javascript
import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = `#graphql
extend type Query {
me: User
user(id: ID!): User
}
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
avatar: String
}
`;
const resolvers = {
Query: {
me: () => users[0],
user: (_, { id }) => users.find(u => u.id === id)
},
User: {
__resolveReference: (user) => {
return users.find(u => u.id === user.id);
}
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
server.listen({ port: 4001 }).then(({ url }) => {
console.log(`Users service ready at ${url}`);
});Product Service
javascript
const typeDefs = `#graphql
extend type Query {
products: [Product]
product(id: ID!): Product
}
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
description: String
}
`;
const resolvers = {
Query: {
products: () => products,
product: (_, { id }) => products.find(p => p.id === id)
},
Product: {
__resolveReference: (product) => {
return products.find(p => p.id === product.id);
}
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
server.listen({ port: 4002 }).then(({ url }) => {
console.log(`Products service ready at ${url}`);
});Order Service (Extending User)
javascript
const typeDefs = `#graphql
extend type Query {
orders: [Order]
order(id: ID!): Order
}
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order]
}
type Order @key(fields: "id") {
id: ID!
total: Float!
items: [OrderItem]
}
type OrderItem {
product: Product
quantity: Int
}
`;
const resolvers = {
User: {
orders: (user) => {
return orders.filter(o => o.userId === user.id);
}
},
Order: {
__resolveReference: (order) => {
return orders.find(o => o.id === order.id);
}
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }])
});
server.listen({ port: 4003 }).then(({ url }) => {
console.log(`Orders service ready at ${url}`);
});Apollo Gateway
Setting Up Gateway
javascript
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
{ name: 'products', url: 'http://localhost:4002/graphql' },
{ name: 'orders', url: 'http://localhost:4003/graphql' }
]
})
});
const server = new ApolloServer({
gateway,
subgraph: {
delay: 5000
}
});
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`Gateway ready at ${url}`);
});Managed Federation
javascript
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { readFileSync } from 'fs';
const gateway = new ApolloGateway({
supergraphSdl: readFileSync('./supergraph.graphql', 'utf-8')
});
const server = new ApolloServer({
gateway
});
server.listen().then(({ url }) => {
console.log(`Gateway ready at ${url}`);
});Entity Extensions
Extending Types
graphql
# In Reviews service
extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review]
}
type Review {
id: ID!
rating: Int
comment: String
author: User
}Multiple Keys
graphql
type User @key(fields: "id") @key(fields: "email") {
id: ID!
email: String!
name: String!
}Query Planning
Efficient Queries
graphql
query GetUserWithOrders($userId: ID!) {
user(id: $userId) {
id
name
email
orders {
id
total
items {
product {
id
name
price
}
}
}
}
}Query Plans
Gateway automatically splits queries:
javascript
# Service 1: Users
{ user(id: "1") { id name email } }
# Service 2: Orders
{ _entities(representations: [{ __typename: "User", id: "1" }]) {
... on User { orders { id total } } } }
# Service 3: Products
{ _entities(representations: [{ __typename: "Product", id: "101" }]) {
... on Product { id name price } } }Best Practices
Service Boundaries
javascript
// Good: Domain-driven boundaries
const services = [
{ name: 'users', domain: 'identity' },
{ name: 'products', domain: 'catalog' },
{ name: 'orders', domain: 'commerce' },
{ name: 'payments', domain: 'billing' }
];
// Bad: Technical boundaries
const services = [
{ name: 'auth', domain: 'security' },
{ name: 'database', domain: 'data' }
];Error Handling
javascript
const gateway = new ApolloGateway({
supergraphSdl,
buildService: ({ name, url }) => {
return new RemoteGraphQLDataSource({
url,
didEncounterError(error) {
console.error(`${name} error:`, error);
}
});
}
});Performance Monitoring
javascript
const gateway = new ApolloGateway({
supergraphSdl,
buildService: ({ url }) => {
return new RemoteGraphQLDataSource({
url,
willSendRequest(request) {
request.http.headers.set('x-trace-id', generateTraceId());
}
});
}
});Conclusion
Apollo Federation enables scalable microservices architecture while maintaining a unified GraphQL API. It's the modern approach to building distributed GraphQL systems.