As applications grow in complexity, microservices and modular architectures become essential. GraphQL Federation allows you to build a distributed GraphQL architecture where different services can contribute to a single GraphQL schema. Apollo Gateway serves as the entry point to this federated schema, enabling seamless interaction between various services.
In this module, we will explore how to set up GraphQL Federation with Apollo Gateway in a NestJS application. We will cover the integration of multiple NestJS services into a unified GraphQL API, ensuring each service handles a distinct domain but appears as one cohesive GraphQL schema to the client.
Table of Contents
- What is GraphQL Federation?
- Setting Up Federation in NestJS
- Apollo Gateway Setup
- Best Practices for GraphQL Federation
- Conclusion
What is GraphQL Federation?
GraphQL Federation is a concept introduced by Apollo that allows you to split your GraphQL API into multiple independent services. These services each define part of the schema, and the Apollo Gateway acts as a unified API that merges these parts together.
Federation is beneficial in microservices architectures because it:
- Decouples different parts of the schema, allowing teams to manage and deploy services independently.
- Allows for scalability since each federated service can be deployed and scaled independently.
- Reduces the complexity of dealing with one large monolithic GraphQL server.
In a federated architecture:
- Each service defines its own schema.
- Apollo Gateway aggregates the schemas and serves as the entry point for clients.
Setting Up Federation in NestJS
Installing Required Dependencies
To use GraphQL Federation with NestJS, we need to install the necessary Apollo packages. These include @nestjs/graphql, @apollo/federation, and @nestjs/apollo.
Install the required dependencies:
bashCopyEditnpm install @nestjs/graphql @nestjs/apollo @apollo/federation apollo-server-express graphql
Creating Federated Services
Each service in a federated architecture can expose part of the schema. Let’s create two services: one for Users and one for Posts. Each service will define part of the GraphQL schema and expose it to the Apollo Gateway.
User Service
tsCopyEdit// user.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './user.model';
@Injectable()
export class UserService {
private readonly users: User[] = [
{ id: '1', name: 'John Doe' },
{ id: '2', name: 'Jane Smith' },
];
findOne(id: string): User {
return this.users.find(user => user.id === id);
}
}
// user.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { UserService } from './user.service';
import { User } from './user.model';
@Resolver(of => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
@Query(returns => User)
async getUser(@Args('id') id: string): Promise<User> {
return this.userService.findOne(id);
}
}
// user.model.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field(type => ID)
id: string;
@Field()
name: string;
}
In this service:
- We define the User schema with a
getUser
query that fetches user data byid
.
Post Service
tsCopyEdit// post.service.ts
import { Injectable } from '@nestjs/common';
import { Post } from './post.model';
@Injectable()
export class PostService {
private readonly posts: Post[] = [
{ id: '1', userId: '1', content: 'Post 1 Content' },
{ id: '2', userId: '2', content: 'Post 2 Content' },
];
findPostsByUserId(userId: string): Post[] {
return this.posts.filter(post => post.userId === userId);
}
}
// post.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { PostService } from './post.service';
import { Post } from './post.model';
import { User } from '../user/user.model';
import { ResolveField, Parent } from '@nestjs/graphql';
@Resolver(of => Post)
export class PostResolver {
constructor(private readonly postService: PostService) {}
@Query(returns => [Post])
async getPostsByUser(@Args('userId') userId: string): Promise<Post[]> {
return this.postService.findPostsByUserId(userId);
}
@ResolveField('user', returns => User)
async getUser(@Parent() post: Post): Promise<User> {
return { id: post.userId, name: 'Sample User' }; // Simulate fetching user
}
}
// post.model.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';
@ObjectType()
export class Post {
@Field(type => ID)
id: string;
@Field()
content: string;
@Field(type => String)
userId: string;
}
In this service:
- We define the Post schema with a
getPostsByUser
query, and use@ResolveField
to link posts to theUser
type.
Apollo Gateway Setup
Setting Up Apollo Gateway
To integrate these services with Apollo Gateway, you need a separate service acting as the gateway. This gateway will aggregate schemas from each service.
First, install the required gateway packages:
bashCopyEditnpm install @nestjs/apollo @nestjs/graphql apollo-server-express graphql
Then, configure the Apollo Gateway in your NestJS application:
tsCopyEdit// gateway.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway';
import { ApolloServer } from 'apollo-server-express';
@Module({
imports: [
GraphQLModule.forRootAsync({
useFactory: () => ({
driver: ApolloServer,
gateway: new ApolloGateway({
serviceList: [
{ name: 'users', url: 'http://localhost:3001/graphql' },
{ name: 'posts', url: 'http://localhost:3002/graphql' },
],
buildService({ name, url }) {
return new RemoteGraphQLDataSource({ url });
},
}),
}),
}),
],
})
export class GatewayModule {}
In this setup:
- Apollo Gateway is configured to aggregate the services from User and Post. The
serviceList
contains the URLs of the federated services. - The
buildService
method is used to define how to connect to each federated service.
Configuring Gateway to Federated Services
Now, run the services independently on different ports:
- User service:
http://localhost:3001/graphql
- Post service:
http://localhost:3002/graphql
The Apollo Gateway will combine these schemas and allow queries to be executed across both services.
Best Practices for GraphQL Federation
- Schema Design: Carefully design your schema to split responsibilities across multiple services. Each service should own its part of the schema.
- Avoid Circular Dependencies: Ensure that services do not have circular dependencies. Use
@ResolveField
to link services where necessary. - Handle Errors Gracefully: Federated services should handle errors gracefully and communicate them to the Apollo Gateway to provide a clear error response to clients.
- Versioning: Keep track of versions of your federated services to ensure backward compatibility.
Conclusion
In this module, we’ve covered the fundamentals of GraphQL Federation with Apollo Gateway in NestJS. We’ve set up a federated architecture where different services contribute to a single GraphQL schema, allowing scalable and modular applications. With Apollo Gateway acting as the central entry point, clients can query data from multiple services seamlessly.