Implementing GraphQL Federation with Apollo Server
Introduction
GraphQL Federation allows multiple GraphQL services to be composed into a single unified schema, enabling microservice architectures with a unified API gateway.
Prerequisites
- Node.js >=14
- Understanding of GraphQL
- Multiple microservices
Step 1: Install Dependencies
# For subgraph services
npm install @apollo/server @apollo/subgraph graphql
# For gateway
npm install @apollo/server @apollo/gateway graphql
Step 2: Create User Subgraph Service
Create services/user-service/src/index.ts
:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
// Type definitions with Federation directives
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external"])
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
profile: UserProfile
}
type UserProfile {
firstName: String
lastName: String
avatar: String
}
type Query {
me: User
user(id: ID!): User
users: [User!]!
}
type Mutation {
updateProfile(input: UpdateProfileInput!): User
}
input UpdateProfileInput {
firstName: String
lastName: String
avatar: String
}
`;
// Mock data
const users = [
{
id: '1',
username: 'john_doe',
email: 'john@example.com',
profile: { firstName: 'John', lastName: 'Doe', avatar: 'avatar1.jpg' }
},
{
id: '2',
username: 'jane_smith',
email: 'jane@example.com',
profile: { firstName: 'Jane', lastName: 'Smith', avatar: 'avatar2.jpg' }
}
];
const resolvers = {
Query: {
me: () => users[0], // Mock current user
user: (_: any, { id }: { id: string }) => users.find(user => user.id === id),
users: () => users
},
Mutation: {
updateProfile: (_: any, { input }: { input: any }) => {
const user = users[0]; // Mock current user
user.profile = { ...user.profile, ...input };
return user;
}
},
User: {
// Federation resolver - allows other services to reference User by id
__resolveReference: (reference: { id: string }) => {
return users.find(user => user.id === reference.id);
}
}
};
async function startUserService() {
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 }
});
console.log(`User service ready at ${url}`);
}
startUserService();
Step 3: Create Posts Subgraph Service
Create services/posts-service/src/index.ts
:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external"])
# Extend User type from user service
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
authorId: String!
author: User!
createdAt: String!
tags: [String!]!
}
type Query {
post(id: ID!): Post
posts: [Post!]!
postsByUser(userId: ID!): [Post!]!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post
deletePost(id: ID!): Boolean!
}
input CreatePostInput {
title: String!
content: String!
tags: [String!] = []
}
input UpdatePostInput {
title: String
content: String
tags: [String!]
}
`;
// Mock data
const posts = [
{
id: '1',
title: 'Getting Started with GraphQL Federation',
content: 'GraphQL Federation allows you to compose multiple GraphQL services...',
authorId: '1',
createdAt: '2025-01-01T00:00:00Z',
tags: ['graphql', 'federation']
},
{
id: '2',
title: 'Node.js Best Practices',
content: 'Here are some best practices for Node.js development...',
authorId: '2',
createdAt: '2025-01-02T00:00:00Z',
tags: ['nodejs', 'javascript']
}
];
const resolvers = {
Query: {
post: (_: any, { id }: { id: string }) => posts.find(post => post.id === id),
posts: () => posts,
postsByUser: (_: any, { userId }: { userId: string }) =>
posts.filter(post => post.authorId === userId)
},
Mutation: {
createPost: (_: any, { input }: { input: any }) => {
const post = {
id: Date.now().toString(),
...input,
authorId: '1', // Mock current user
createdAt: new Date().toISOString()
};
posts.push(post);
return post;
},
updatePost: (_: any, { id, input }: { id: string; input: any }) => {
const postIndex = posts.findIndex(post => post.id === id);
if (postIndex === -1) return null;
posts[postIndex] = { ...posts[postIndex], ...input };
return posts[postIndex];
},
deletePost: (_: any, { id }: { id: string }) => {
const postIndex = posts.findIndex(post => post.id === id);
if (postIndex === -1) return false;
posts.splice(postIndex, 1);
return true;
}
},
Post: {
author: (post: any) => ({ __typename: 'User', id: post.authorId }),
__resolveReference: (reference: { id: string }) => {
return posts.find(post => post.id === reference.id);
}
},
User: {
posts: (user: any) => posts.filter(post => post.authorId === user.id)
}
};
async function startPostsService() {
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4002 }
});
console.log(`Posts service ready at ${url}`);
}
startPostsService();
Step 4: Create Gateway Service
Create gateway/src/index.ts
:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
async function startGateway() {
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001' },
{ name: 'posts', url: 'http://localhost:4002' }
]
}),
// Optional: Add custom directives or plugins
buildService: ({ url }) => {
return new (require('@apollo/datasource-rest').RESTDataSource)({
baseURL: url,
});
}
});
const server = new ApolloServer({
gateway,
// Disable subscription support for now
subscriptions: false,
plugins: [
// Add custom plugins for logging, caching, etc.
{
requestDidStart() {
return {
didResolveOperation(context) {
console.log('Query:', context.request.query);
}
};
}
}
]
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => {
// Add authentication context
const token = req.headers.authorization?.replace('Bearer ', '');
return {
user: token ? { id: '1' } : null // Mock user from token
};
}
});
console.log(`Gateway ready at ${url}`);
console.log('Query the federated schema with:');
console.log(`curl -X POST ${url} -H "Content-Type: application/json" -d '{"query":"{users{id username posts{title}}}"}'`);
}
startGateway();
Step 5: Schema Composition with Rover CLI
Install Apollo Rover:
npm install -g @apollo/rover
Create supergraph configuration:
federation_version: =2.3.0
subgraphs:
users:
routing_url: http://localhost:4001
schema:
subgraph_url: http://localhost:4001
posts:
routing_url: http://localhost:4002
schema:
subgraph_url: http://localhost:4002
Generate supergraph schema:
rover supergraph compose --config ./supergraph-config.yaml > supergraph-schema.graphql
Step 6: Advanced Federation Features
Entity relationships and computed fields:
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@requires"])
extend type Post @key(fields: "id") {
id: ID! @external
title: String! @external
viewCount: Int! @requires(fields: "id")
popularity: Float! @requires(fields: "id title")
}
extend type User @key(fields: "id") {
id: ID! @external
analytics: UserAnalytics!
}
type UserAnalytics {
totalViews: Int!
totalPosts: Int!
averageEngagement: Float!
}
`;
const resolvers = {
Post: {
viewCount: async (post: any) => {
// Fetch view count from analytics service
return Math.floor(Math.random() * 1000);
},
popularity: async (post: any) => {
// Calculate popularity based on views and title keywords
const baseScore = post.title.length * 0.1;
const viewBonus = Math.random() * 10;
return Number((baseScore + viewBonus).toFixed(2));
}
},
User: {
analytics: async (user: any) => {
return {
totalViews: Math.floor(Math.random() * 10000),
totalPosts: Math.floor(Math.random() * 50),
averageEngagement: Number((Math.random() * 100).toFixed(2))
};
}
}
};
Step 7: Error Handling and Monitoring
export const errorHandlingPlugin = {
requestDidStart() {
return {
didEncounterErrors(context: any) {
context.errors.forEach((error: any) => {
console.error('GraphQL Error:', {
message: error.message,
path: error.path,
service: error.extensions?.serviceName,
timestamp: new Date().toISOString()
});
});
}
};
}
};
Step 8: Example Federated Queries
# Query spanning multiple services
query GetUserWithPosts {
user(id: "1") {
id
username
email
profile {
firstName
lastName
}
posts {
id
title
content
createdAt
tags
}
}
}
# Query with computed fields
query GetPostAnalytics {
posts {
id
title
viewCount
popularity
author {
username
analytics {
totalViews
averageEngagement
}
}
}
}
Summary
GraphQL Federation with Apollo Server enables microservice composition through a unified schema. Each service owns its domain while the gateway provides a single entry point, supporting entity relationships and computed fields across service boundaries.