Federation
Federation lets you split a monolithic GraphQL server into independent microservices. It consists of two components: a gateway and one or more federated microservices. Each microservice holds part of the schema, and the gateway merges the schemas into a single schema that clients consume.
As described in Apollo's introduction to federation, federation is designed around these core principles:
- Building a graph should be declarative. With federation, you compose a graph declaratively from within your schema instead of writing imperative schema stitching code.
- Code should be separated by concern, not by types. Often no single team controls every aspect of an important type like a User or Product, so the definition of these types should be distributed across teams and codebases, rather than centralized.
- The graph should be simple for clients to consume. Together, federated services can form a complete, product-focused graph that accurately reflects how it's being consumed on the client.
- It's just GraphQL, using only spec-compliant features of the language. Any language, not just JavaScript, can implement federation.
Warning Federation currently does not support subscriptions.
In the following sections, we'll set up a demo application that consists of a gateway and two federated endpoints: a Users service and a Posts service.
Federation with Apollo#
Start by installing the required dependencies:
$ npm install --save @apollo/subgraph
Schema first#
The Users service provides a simple schema. The @key directive tells the Apollo query planner that a particular instance of User can be fetched by its id. Also note that the schema extends the Query type.
type User @key(fields: "id") {
id: ID!
name: String!
}
extend type Query {
getUser(id: ID!): User
}
The resolver provides one additional method named resolveReference(). The Apollo gateway calls this method whenever a related resource requires a User instance. You'll see an example of this in the Posts service later. The method must be annotated with the @ResolveReference() decorator.
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { UsersService } from './users.service.js';
@Resolver('User')
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query()
getUser(@Args('id') id: string) {
return this.usersService.findById(id);
}
@ResolveReference()
resolveReference(reference: { __typename: string; id: string }) {
return this.usersService.findById(reference.id);
}
}
Finally, wire everything up by registering the GraphQLModule with the ApolloFederationDriver driver in the configuration object:
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [UsersResolver],
})
export class AppModule {}
Code first#
Start by adding some extra decorators to the User entity:
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
}
The resolver provides one additional method named resolveReference(). The Apollo gateway calls this method whenever a related resource requires a User instance. You'll see an example of this in the Posts service later. The method must be annotated with the @ResolveReference() decorator.
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { UsersService } from './users.service.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => User)
getUser(@Args('id') id: number): User {
return this.usersService.findById(id);
}
@ResolveReference()
resolveReference(reference: { __typename: string; id: number }): User {
return this.usersService.findById(reference.id);
}
}
Finally, wire everything up by registering the GraphQLModule with the ApolloFederationDriver driver in the configuration object:
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
import { UsersService } from './users.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: true,
}),
],
providers: [UsersResolver, UsersService],
})
export class AppModule {}
Working examples are available for the code first and schema first approaches.
Federated example: Posts#
The Posts service serves aggregated posts through the getPosts query, and also extends the User type with a user.posts field.
Schema first#
The Posts service references the User type in its schema by marking it with the extend keyword. It also declares one additional field on the User type (posts). Note the @key directive, used to match User instances, and the @external directive, which indicates that the id field is managed elsewhere.
type Post @key(fields: "id") {
id: ID!
title: String!
body: String!
user: User
}
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post]
}
extend type Query {
getPosts: [Post]
}
In the following example, the PostsResolver provides a getUser() method that returns a reference containing __typename plus any additional properties your application needs to resolve the reference, in this case id. The gateway uses __typename to pinpoint the microservice responsible for the User type and retrieve the corresponding instance. To resolve the reference, the gateway sends a request to the Users service described above, which executes its resolveReference() method.
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import type { Post } from './posts.interfaces.js';
@Resolver('Post')
export class PostsResolver {
constructor(private postsService: PostsService) {}
@Query('getPosts')
getPosts() {
return this.postsService.findAll();
}
@ResolveField('user')
getUser(@Parent() post: Post) {
return { __typename: 'User', id: post.userId };
}
}
Lastly, register the GraphQLModule, as in the Users service.
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { PostsResolver } from './posts.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [PostsResolver],
})
export class AppModule {}
Code first#
First, declare a class representing the User entity. Although the entity itself lives in another service, this service uses it (and extends its definition). Note the @extends and @external directives.
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from './post.entity.js';
@ObjectType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
@Directive('@external')
id: number;
@Field(() => [Post])
posts?: Post[];
}
Next, create the resolver for the extension of the User entity:
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly postsService: PostsService) {}
@ResolveField(() => [Post])
public posts(@Parent() user: User): Post[] {
return this.postsService.forAuthor(user.id);
}
}
Then define the Post entity class:
import { Directive, Field, ID, Int, ObjectType } from '@nestjs/graphql';
import { User } from './user.entity.js';
@ObjectType()
@Directive('@key(fields: "id")')
export class Post {
@Field(() => ID)
id: number;
@Field()
title: string;
@Field(() => Int)
authorId: number;
@Field(() => User)
user?: User;
}
And its resolver:
import { Query, Args, ResolveField, Resolver, Parent } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => Post)
export class PostsResolver {
constructor(private readonly postsService: PostsService) {}
@Query(() => Post)
findPost(@Args('id') id: number): Post {
return this.postsService.findOne(id);
}
@Query(() => [Post])
getPosts(): Post[] {
return this.postsService.all();
}
@ResolveField(() => User)
user(@Parent() post: Post): any {
return { __typename: 'User', id: post.authorId };
}
}
Finally, tie everything together in a module. Note the schema build options, which specify that User is an orphaned (external) type.
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { PostsResolver } from './posts.resolver.js';
import { UsersResolver } from './users.resolver.js';
import { PostsService } from './posts.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: true,
buildSchemaOptions: {
orphanedTypes: [User],
},
}),
],
providers: [PostsResolver, UsersResolver, PostsService],
})
export class AppModule {}
Working examples are available for the code first and schema first approaches.
Federated example: Gateway#
Start by installing the required dependency:
$ npm install --save @apollo/gateway
The gateway requires a list of endpoints, and it discovers the corresponding schemas automatically. Therefore, the gateway implementation is the same for both the code first and schema first approaches.
import { IntrospectAndCompose } from '@apollo/gateway';
import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
@Module({
imports: [
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
driver: ApolloGatewayDriver,
server: {
// ... Apollo Server options
},
gateway: {
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://user-service/graphql' },
{ name: 'posts', url: 'http://post-service/graphql' },
],
}),
},
}),
],
})
export class AppModule {}
Working examples are available for the code first and schema first approaches.
Federation with Mercurius#
Start by installing the required dependencies:
$ npm install --save @apollo/subgraph @nestjs/mercurius
Note The@apollo/subgraphpackage is required to build a subgraph schema (thebuildSubgraphSchemaandprintSubgraphSchemafunctions).
Schema first#
The Users service provides a simple schema. The @key directive tells the Mercurius query planner that a particular instance of User can be fetched by its id. Also note that the schema extends the Query type.
type User @key(fields: "id") {
id: ID!
name: String!
}
extend type Query {
getUser(id: ID!): User
}
The resolver provides one additional method named resolveReference(). The Mercurius gateway calls this method whenever a related resource requires a User instance. You'll see an example of this in the Posts service later. The method must be annotated with the @ResolveReference() decorator.
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { UsersService } from './users.service.js';
@Resolver('User')
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query()
getUser(@Args('id') id: string) {
return this.usersService.findById(id);
}
@ResolveReference()
resolveReference(reference: { __typename: string; id: string }) {
return this.usersService.findById(reference.id);
}
}
Finally, wire everything up by registering the GraphQLModule with the MercuriusFederationDriver driver in the configuration object:
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [UsersResolver],
})
export class AppModule {}
Code first#
Start by adding some extra decorators to the User entity:
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
}
The resolver provides one additional method named resolveReference(). The Mercurius gateway calls this method whenever a related resource requires a User instance. You'll see an example of this in the Posts service later. The method must be annotated with the @ResolveReference() decorator.
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { UsersService } from './users.service.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => User)
getUser(@Args('id') id: number): User {
return this.usersService.findById(id);
}
@ResolveReference()
resolveReference(reference: { __typename: string; id: number }): User {
return this.usersService.findById(reference.id);
}
}
Finally, wire everything up by registering the GraphQLModule with the MercuriusFederationDriver driver in the configuration object:
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
import { UsersService } from './users.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
autoSchemaFile: true,
}),
],
providers: [UsersResolver, UsersService],
})
export class AppModule {}
Federated example: Posts#
The Posts service serves aggregated posts through the getPosts query, and also extends the User type with a user.posts field.
Schema first#
The Posts service references the User type in its schema by marking it with the extend keyword. It also declares one additional field on the User type (posts). Note the @key directive, used to match User instances, and the @external directive, which indicates that the id field is managed elsewhere.
type Post @key(fields: "id") {
id: ID!
title: String!
body: String!
user: User
}
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post]
}
extend type Query {
getPosts: [Post]
}
In the following example, the PostsResolver provides a getUser() method that returns a reference containing __typename plus any additional properties your application needs to resolve the reference, in this case id. The gateway uses __typename to pinpoint the microservice responsible for the User type and retrieve the corresponding instance. To resolve the reference, the gateway sends a request to the Users service described above, which executes its resolveReference() method.
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import type { Post } from './posts.interfaces.js';
@Resolver('Post')
export class PostsResolver {
constructor(private postsService: PostsService) {}
@Query('getPosts')
getPosts() {
return this.postsService.findAll();
}
@ResolveField('user')
getUser(@Parent() post: Post) {
return { __typename: 'User', id: post.userId };
}
}
Lastly, register the GraphQLModule, as in the Users service.
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { PostsResolver } from './posts.resolver.js';
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [PostsResolver],
})
export class AppModule {}
Code first#
First, declare a class representing the User entity. Although the entity itself lives in another service, this service uses it (and extends its definition). Note the @extends and @external directives.
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from './post.entity.js';
@ObjectType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
@Directive('@external')
id: number;
@Field(() => [Post])
posts?: Post[];
}
Next, create the resolver for the extension of the User entity:
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly postsService: PostsService) {}
@ResolveField(() => [Post])
public posts(@Parent() user: User): Post[] {
return this.postsService.forAuthor(user.id);
}
}
Then define the Post entity class:
import { Directive, Field, ID, Int, ObjectType } from '@nestjs/graphql';
import { User } from './user.entity.js';
@ObjectType()
@Directive('@key(fields: "id")')
export class Post {
@Field(() => ID)
id: number;
@Field()
title: string;
@Field(() => Int)
authorId: number;
@Field(() => User)
user?: User;
}
And its resolver:
import { Query, Args, ResolveField, Resolver, Parent } from '@nestjs/graphql';
import { PostsService } from './posts.service.js';
import { Post } from './post.entity.js';
import { User } from './user.entity.js';
@Resolver(() => Post)
export class PostsResolver {
constructor(private readonly postsService: PostsService) {}
@Query(() => Post)
findPost(@Args('id') id: number): Post {
return this.postsService.findOne(id);
}
@Query(() => [Post])
getPosts(): Post[] {
return this.postsService.all();
}
@ResolveField(() => User)
user(@Parent() post: Post): any {
return { __typename: 'User', id: post.authorId };
}
}
Finally, tie everything together in a module. Note the schema build options, which specify that User is an orphaned (external) type.
import {
MercuriusFederationDriver,
MercuriusFederationDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { PostsResolver } from './posts.resolver.js';
import { UsersResolver } from './users.resolver.js';
import { PostsService } from './posts.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<MercuriusFederationDriverConfig>({
driver: MercuriusFederationDriver,
autoSchemaFile: true,
buildSchemaOptions: {
orphanedTypes: [User],
},
}),
],
providers: [PostsResolver, UsersResolver, PostsService],
})
export class AppModule {}
Federated example: Gateway#
Start by installing the required dependency:
$ npm install --save @mercuriusjs/gateway
The gateway requires a list of endpoints, and it discovers the corresponding schemas automatically. Therefore, the gateway implementation is the same for both the code first and schema first approaches.
import {
MercuriusGatewayDriver,
MercuriusGatewayDriverConfig,
} from '@nestjs/mercurius';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
@Module({
imports: [
GraphQLModule.forRoot<MercuriusGatewayDriverConfig>({
driver: MercuriusGatewayDriver,
gateway: {
services: [
{ name: 'users', url: 'http://user-service/graphql' },
{ name: 'posts', url: 'http://post-service/graphql' },
],
},
}),
],
})
export class AppModule {}
Federation 2
According to the Apollo docs, Federation 2 improves the developer experience of the original Apollo Federation (called Federation 1 in this section) and is backward compatible with most original supergraphs.
Warning Mercurius doesn't fully support Federation 2. See Apollo's list of subgraph libraries that support Federation 2.
In the following sections, we'll upgrade the previous example to Federation 2.
Federated example: Users#
In Federation 2, entities have no originating subgraph, so you no longer need to extend Query. For more details, see the entities topic in the Apollo Federation 2 docs.
Schema first#
Remove the extend keyword from the schema. To opt in to Federation 2, the schema must also import the federation directives it uses with the @link directive:
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
type User @key(fields: "id") {
id: ID!
name: String!
}
type Query {
getUser(id: ID!): User
}
Code first#
To use Federation 2, specify the federation version in the autoSchemaFile option. Nest then adds the @link directive to the generated schema for you.
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UsersResolver } from './users.resolver.js';
import { UsersService } from './users.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
}),
],
providers: [UsersResolver, UsersService],
})
export class AppModule {}
Federated example: Posts#
For the same reason, you no longer need to extend User and Query.
Schema first#
Remove the extend keyword and the @external directive from the schema, and add the @link directive:
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
type Post @key(fields: "id") {
id: ID!
title: String!
body: String!
user: User
}
type User @key(fields: "id") {
id: ID!
posts: [Post]
}
type Query {
getPosts: [Post]
}
Code first#
Because the User entity is no longer extended, remove the @extends and @external directives from User:
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
import { Post } from './post.entity.js';
@ObjectType()
@Directive('@key(fields: "id")')
export class User {
@Field(() => ID)
id: number;
@Field(() => [Post])
posts?: Post[];
}
As in the Users service, configure the GraphQLModule to use Federation 2:
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { User } from './user.entity.js';
import { PostsResolver } from './posts.resolver.js';
import { UsersResolver } from './users.resolver.js';
import { PostsService } from './posts.service.js'; // Not included in this example
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
buildSchemaOptions: {
orphanedTypes: [User],
},
}),
],
providers: [PostsResolver, UsersResolver, PostsService],
})
export class AppModule {}

