GraphQL Federation enables you to compose multiple GraphQL services into a unified graph, perfect for microservices architectures.
What Is GraphQL Federation?
Federation splits a monolithic GraphQL schema into smaller, deployable services that can be developed independently.
Traditional Monolithic GraphQL:
┌─────────────────────────┐
│ GraphQL Gateway │
│ (All resolvers here) │
└───────────┬─────────────┘
│
┌───────┴───────┐
│ │
┌───┴───┐ ┌───┴───┐
│ Users │ │ Posts │
│Service│ │Service│
└───────┘ └───────┘
GraphQL Federation:
┌─────────────────────────┐
│ Apollo Gateway │
│ (Composition only) │
└───────────┬─────────────┘
│
┌───────┴───────┐
│ │
┌───┴───────┐ ┌───┴───────┐
│ Users │ │ Posts │
│Subgraph │ │Subgraph │
└───────────┘ └───────────┘Core Concepts
Subgraphs
Each microservice is a subgraph with its own schema.
# Users subgraph
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
}
# Posts subgraph
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
author: User @provides(fields: "name")
}
type User @key(fields: "id") {
id: ID!
name: String!
}
type Query {
post(id: ID!): Post
}Entities
Entities are types that can be extended across subgraphs.
# Define entity with @key
type User @key(fields: "id") {
id: ID!
name: String!
}
# Extend entity in another subgraph
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post]
}Setting Up Federation
Users Subgraph
// users-service/index.js
const { ApolloServer } = require('@apollo/server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = `#graphql
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User]
}
`;
const resolvers = {
Query: {
user: (_, { id }) => users.find(u => u.id === id),
users: () => users,
},
User: {
__resolveReference: (user) => users.find(u => u.id === user.id),
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
server.listen({ port: 4001 }).then(({ url }) => {
console.log(`Users subgraph ready at ${url}`);
});Posts Subgraph
// posts-service/index.js
const { ApolloServer } = require('@apollo/server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = `#graphql
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
author: User
}
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post]
}
type Query {
post(id: ID!): Post
posts: [Post]
}
`;
const resolvers = {
Query: {
post: (_, { id }) => posts.find(p => p.id === id),
posts: () => posts,
},
Post: {
author: (post) => ({ __typename: 'User', id: post.authorId }),
},
User: {
posts: (user) => posts.filter(p => p.authorId === user.id),
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
server.listen({ port: 4002 }).then(({ url }) => {
console.log(`Posts subgraph ready at ${url}`);
});Apollo Gateway
// gateway/index.js
const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('@apollo/server');
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001' },
{ name: 'posts', url: 'http://localhost:4002' },
],
}),
});
const server = new ApolloServer({
gateway,
});
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`Gateway ready at ${url}`);
});Federation Directives
@key
Defines the primary key for an entity.
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
}
# Composite key
type Product @key(fields: "id category") {
id: ID!
category: String!
name: String!
}@external
Marks a field as defined in another subgraph.
extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review]
}@provides
Specifies fields that a subgraph can provide.
type Review {
id: ID!
product: Product @provides(fields: "price")
}
extend type Product @key(fields: "id") {
id: ID! @external
price: Float @external
}@requires
Specifies fields required from another subgraph.
extend type User @key(fields: "id") {
id: ID! @external
email: String! @external
fullName: String! @requires(fields: "email")
}Entity Resolution
Reference Resolution
const resolvers = {
User: {
__resolveReference: (reference, context) => {
// reference = { id: "123" }
return fetchUserById(reference.id);
},
},
};With DataLoader
const DataLoader = require('dataloader');
const userBatchLoader = new DataLoader(async (userIds) => {
const users = await fetchUsersByIds(userIds);
return userIds.map(id => users.find(u => u.id === id));
});
const resolvers = {
User: {
__resolveReference: (reference) => {
return userBatchLoader.load(reference.id);
},
},
};Relationships
One-to-One
# Users subgraph
type User @key(fields: "id") {
id: ID!
profile: Profile
}
type Profile @key(fields: "userId") {
userId: ID!
bio: String!
}
# Profiles subgraph
extend type User @key(fields: "id") {
id: ID! @external
profile: Profile
}
type Profile @key(fields: "userId") {
userId: ID!
user: User! @provides(fields: "id")
}One-to-Many
# Users subgraph
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post]
}
# Posts subgraph
type Post @key(fields: "id") {
id: ID!
author: User!
}
extend type User @key(fields: "id") {
id: ID! @external
}Many-to-Many
# Posts subgraph
extend type Post @key(fields: "id") {
id: ID! @external
tags: [Tag]
}
# Tags subgraph
type Tag @key(fields: "id") {
id: ID!
name: String!
posts: [Post]
}
extend type Post @key(fields: "id") {
id: ID! @external
}Best Practices
DOs
- Design subgraphs around business domains
- Use entities for shared types
- Implement proper error handling
- Use DataLoader for batching
- Monitor subgraph performance
- Version your subgraphs
- Test federated queries
DON'Ts
- Create circular dependencies
- Over-fetch across subgraphs
- Ignore N+1 queries
- Skip caching
- Forget about security
- Mix concerns
- Create tight coupling
Error Handling
Partial Errors
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
try {
return await dataSources.usersAPI.getUser(id);
} catch (error) {
// Return partial data
return {
id,
name: null,
error: 'Failed to fetch user',
};
}
},
},
};Conclusion
GraphQL Federation enables scalable microservices architecture with a unified GraphQL API. Design subgraphs around domains, use entities for shared types, and optimize queries. Your teams can work independently while providing a seamless API!