Skip to content
NestJS ns graphql 4 min read

GraphQL Federation

As an API grows, keeping one monolithic GraphQL schema becomes a bottleneck: every team ships to the same codebase and a single resolver layer owns every type. Apollo Federation solves this by splitting the graph into independently deployed subgraphs that a gateway composes into one unified supergraph. Each service owns the types and fields it is responsible for, yet clients still query a single endpoint. NestJS supports federation natively through @nestjs/graphql and the Apollo Federation driver, so you build subgraphs the same way you build any code-first schema.

Installing the federation drivers

Federation in NestJS uses dedicated drivers built on top of @apollo/subgraph and @apollo/gateway. Install the packages alongside the standard GraphQL tooling.

npm install @nestjs/graphql @nestjs/apollo @apollo/subgraph @apollo/gateway graphql

Each subgraph is a normal Nest application that registers GraphQLModule with ApolloFederationDriver. The gateway is a separate Nest application that uses ApolloGatewayDriver and never defines types of its own.

Building a subgraph

A subgraph declares the entities it owns and exposes a federated schema. Mark an object type with @Directive('@key(...)') to declare its entity key — the field(s) the gateway uses to identify and merge instances of that type across services. Here is a users subgraph.

// users/user.entity.ts
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';

@ObjectType()
@Directive('@key(fields: "id")')
export class User {
  @Field(() => ID)
  id: string;

  @Field()
  name: string;

  @Field()
  email: string;
}

The resolver provides both the normal query fields and a reference resolver. The __resolveReference method is invoked by the gateway when another subgraph references a User by its key — it receives the key fields and returns the full entity.

// users/users.resolver.ts
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from './user.entity';

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly usersService: UsersService) {}

  @Query(() => User, { nullable: true })
  user(@Args('id') id: string): User | undefined {
    return this.usersService.findById(id);
  }

  @ResolveReference()
  resolveReference(reference: { __typename: string; id: string }): User | undefined {
    return this.usersService.findById(reference.id);
  }
}

Register the subgraph with the federation driver. Setting autoSchemaFile to true keeps the generated SDL in memory, which is convenient during development.

// users/app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import {
  ApolloFederationDriver,
  ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { UsersResolver } from './users.resolver';
import { UsersService } from './users.service';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloFederationDriverConfig>({
      driver: ApolloFederationDriver,
      autoSchemaFile: { federation: 2 },
    }),
  ],
  providers: [UsersResolver, UsersService],
})
export class AppModule {}

Extending an entity from another subgraph

A second subgraph can add fields to a type it does not own. The posts subgraph references User as an external entity and contributes an author relationship, while extending User with a posts field.

// posts/post.entity.ts
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';

@ObjectType()
@Directive('@key(fields: "id")')
export class Post {
  @Field(() => ID)
  id: string;

  @Field()
  title: string;

  @Field(() => User)
  author: User;
}

@ObjectType()
@Directive('@key(fields: "id")')
export class User {
  @Field(() => ID)
  @Directive('@external')
  id: string;

  @Field(() => [Post])
  posts: Post[];
}

The posts subgraph resolves only the posts field for User; the gateway fetches the rest from the users subgraph using the shared id key.

// posts/posts.resolver.ts
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import { PostsService } from './posts.service';
import { Post, User } from './post.entity';

@Resolver(() => User)
export class UserPostsResolver {
  constructor(private readonly postsService: PostsService) {}

  @ResolveField(() => [Post])
  posts(@Parent() user: User): Post[] {
    return this.postsService.findByAuthor(user.id);
  }
}

Composing with the gateway

The gateway points at each running subgraph and merges their schemas. Use IntrospectAndCompose for local development; in production you would publish to a managed schema registry instead.

// gateway/app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo';
import { IntrospectAndCompose } from '@apollo/gateway';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
      driver: ApolloGatewayDriver,
      server: { path: '/graphql' },
      gateway: {
        supergraphSdl: new IntrospectAndCompose({
          subgraphs: [
            { name: 'users', url: 'http://localhost:3001/graphql' },
            { name: 'posts', url: 'http://localhost:3002/graphql' },
          ],
        }),
      },
    }),
  ],
})
export class AppModule {}

A client query against the gateway transparently fans out to both subgraphs.

query {
  user(id: "1") {
    name
    posts { title }
  }
}

Output:

{
  "data": {
    "user": {
      "name": "Ada Lovelace",
      "posts": [{ "title": "On the Analytical Engine" }]
    }
  }
}

Federation 2 (autoSchemaFile: { federation: 2 }) is recommended for new projects. It relaxes the strict extend/@external rules of v1 and supports shared types, so prefer it unless you must interoperate with a v1 supergraph.

ConceptDirective / APIOwned by
Entity key@Directive('@key(fields: "id")')The defining subgraph
External field@Directive('@external')Borrowing subgraph
Reference resolution@ResolveReference()Subgraph that owns the entity
CompositionIntrospectAndCompose / registryGateway

Best practices

  • Give each entity a stable, minimal @key (often an ID); avoid keys built from mutable business fields.
  • Keep one owning subgraph per entity. Other subgraphs should only extend it with their own fields, never redefine ownership.
  • Always implement __resolveReference for any entity another subgraph references, or cross-service joins will return null.
  • Prefer Federation 2 and a managed schema registry over IntrospectAndCompose in production to catch composition errors before deploy.
  • Keep reference resolvers cheap and batch lookups (e.g. with DataLoader) since the gateway may resolve many entities per request.
  • Version subgraph schemas independently and run composition checks in CI so a breaking change in one service cannot poison the supergraph.
Last updated June 14, 2026
Was this helpful?