Skip to main content

Federation

nestjs-query provides basic federation support, with the intention of helping to

  • Plug into existing federated graphs, through references.
  • Create a federated relations/connections on types defined in other services.

These docs assume you have read

Base Type#

The simplest way to integrate with a federated graph is through references.

A reference is an object that looks like

{ __typename: 'TodoItem', id: subTask.todoItemId }

The __typename lets the gateway know which type is being referenced with additional fields that can be used to uniquely identify the type.

note

Both of the examples below add a resolveReference function see https://www.apollographql.com/docs/apollo-server/federation/entities/#resolving

To reference a type in nestjs-query you must first create DTO that defines the base type.

Base Type#

The base type in its own service must be decorated with federated directives specifying its key.

todo-item/todo-item.dto.ts
import { FilterableField } from '@nestjs-query/query-graphql';import { ObjectType, ID, GraphQLISODateTime, Directive } from '@nestjs/graphql';
@ObjectType('TodoItem')@Directive('@key(fields: "id")')export class TodoItemDTO {  @FilterableField(() => ID)  id!: number;  ...}

Auto Generated Resolver#

When using the NestjsQueryGraphQLModule module add the referenceBy option that nestjs-query will use to automatically expose add a @ResolveReference decorator and method that the gateway can use.

todo-item/todo-item.module.ts
import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql';import { Module } from '@nestjs/common';import { TodoItemDTO } from './todo-item.dto';import { TodoItemEntity } from './todo-item.entity';
@Module({  imports: [    NestjsQueryGraphQLModule.forFeature({      imports: [ /* import the nestjs-query persistence module*/],      resolvers: [        {          DTOClass: TodoItemDTO,          EntityClass: TodoItemEntity,          // specify the referenceBy option letting nestjs-query know to to resolve a reference          referenceBy: { key: 'id' },        },      ],    }),  ],})export class TodoItemModule {}
note

The referenceBy.key should be the field you want to look up the DTO by.

Manual Resolver#

If you want to manually define your resolver pass in the referenceBy option to the CRUDResolver.

todo-item.resolver.ts
@Resolver(() => TodoItemDTO)export class TodoItemResolver extends CRUDResolver(TodoItemDTO, {  // specify the referenceBy option letting nestjs-query know to to resolve a reference  referenceBy: { key: 'id' },}) {  constructor(@InjectQueryService(TodoItemEntity) readonly service: QueryService<TodoItemEntity>) {    super(service);  }}
note

The referenceBy.key should be the field you want to look up the DTO by.

App Module#

This app module must also use the GraphQLFederationModule in order for the base type to be resolved by the gateway.

app.module.ts
import { GraphQLFederationModule } from '@nestjs/graphql';
@Module({  imports: [    TypeOrmModule.forRoot(ormconfig as TypeOrmModuleOptions),    GraphQLFederationModule.forRoot({      autoSchemaFile: 'schema.gql',    }),    TodoItemModule,  ],})export class AppModule {}

Reference Base Type#

In a separate service from the one defining the base type above, we can use Apollo Federation to extend that base type.

To do this with nestjs-query you must create a type that extends the base type contained in some other graphql service.

warning

The type name must be the same name as the type it extends in the graph.

For example

sub-task/todo-item-reference.dto.ts
import { ObjectType, Directive, Field, ID } from '@nestjs/graphql';
@ObjectType('TodoItem')@Directive('@extends')@Directive('@key(fields: "id")')export class TodoItemReferenceDTO {  @Field(() => ID)  @Directive('@external')  id!: number;}
note

Notice how the @Directive decorator is used to add the @extends annotation along with the @keys.

To read more about @extends annotation see https://www.apollographql.com/docs/apollo-server/federation/entities/#extending

@Reference Decorator#

To reference a type defined in another service you can use the @Reference decorator.

When using the @Reference decorator nestjs-query will NOT look up the relation through the QueryService, instead return a reference type like the one described above.

sub-task/sub-task.dto.ts
import { FilterableField, Reference } from '@nestjs-query/query-graphql';import { ObjectType, ID, GraphQLISODateTime } from '@nestjs/graphql';
@ObjectType('SubTask')// add a todoItem reference and use the subTask.todoItemId as the id@Reference('todoItem', () => TodoItemReferenceDTO, { id: 'todoItemId' })export class SubTaskDTO {  @FilterableField(() => ID)  id!: number;
  @FilterableField()  title!: string;
  @FilterableField({ nullable: true })  description?: string;
  @FilterableField()  completed!: boolean;
  @FilterableField(() => GraphQLISODateTime)  created!: Date;
  @FilterableField(() => GraphQLISODateTime)  updated!: Date;
  @FilterableField()  todoItemId!: string;}

In the above example we provided the keys which look like the following

{ id: 'todoItemId' }

Which will map the SubTask.todoItemId to the id field in the reference type.

Assuming you have the following SubTask

{id: 1, title: 'Sub Task 1', completed: false, todoItemId: 2}

The reference type would be

{ __typename: 'TodoItem', id: 2 }

Resolver#

Now that we have added the decorator the nestjs-query resolver will automatically add the reference to the graphql type when using NestjsQueryGraphQLModule or CRUDResolver

sub-task/sub-task.module.ts
import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql';import { Module } from '@nestjs/common';import { SubTaskDTO } from './dto/sub-task.dto';import { SubTaskEntity } from './sub-task.entity';
@Module({  imports: [    NestjsQueryGraphQLModule.forFeature({      imports: [ /* import the nestjs-query persistence module*/],      resolvers: [{ DTOClass: SubTaskDTO, EntityClass: SubTaskEntity }],    }),  ],})export class SubTaskModule {}

Federated Relations#

Another common use case is to add relations to a federated type from another service.

Lets continue with the SubTask example used above. We have add a todoItem reference to the SubTask but now lets add subTasks to the TodoItem.

RelationQueryService#

The first step is to create a RelationQueryService. The RelationQueryService is a special type of QueryService that allows looking up relations without defining them in your entity.

todo-item.service.ts
import { InjectQueryService, QueryService, RelationQueryService } from '@nestjs-query/core';import { TodoItemReferenceDTO } from './todo-item-reference.dto';import { SubTaskEntity } from './sub-task.entity';
@QueryService(TodoItemReferenceDTO)export class TodoItemService extends RelationQueryService<TodoItemReferenceDTO> {  constructor(@InjectQueryService(SubTaskEntity) readonly subTaskService: QueryService<SubTaskEntity>) {    super({      // the name of the relation      subTasks: {        service: subTaskService,        // a query factory that will take in the reference to create a query.        query: (todoItemReferenceDTO) => ({ filter: { todoItemId: { eq: todoItemReferenceDTO.id } } }),      },    });  }}

In this example we inject a SubTask service that will be used to look up subTask relations. The query method is used to filter relations when findRelation or queryRelations is called.

{  // the name of the relation  subTasks: {    // the service to delegate to when looking up the relations    service: subTaskService,    // a query factory that will take in the reference to create a query.    query: (todoItemReferenceDTO) => ({ filter: { todoItemId: { eq: todoItemReferenceDTO.id } } }),  },}

Add the Connection#

Next we add the subTasks connection to the TodoItemReferenceDTO.

note

The name of the relation should match the name of the relation defined by your RelationQueryService.

note

The same pattern applies when you have a single relation and use the @Relation decorator.

sub-task/todo-item-reference.dto.ts
import { Connection } from '@nestjs-query/query-graphql';import { ObjectType, Directive, Field, ID } from '@nestjs/graphql';import { SubTaskDTO } from './sub-task.dto';
@ObjectType('TodoItem')@Directive('@extends')@Directive('@key(fields: "id")')@CursorConnection('subTasks', () => SubTaskDTO)export class TodoItemReferenceDTO {  @Field(() => ID)  @Directive('@external')  id!: number;}

Federation Resolver#

Next we set up our resolver that exposes the relations in the schema. As with other resolvers you can use the NestjsQueryGraphQLModule or define your own FederationResolver.

When using the NestjsQueryGraphQLModule set the type of the resolver to federated, and specify the Service.

Also, add the TodoItemService to the services option to make it available for nest to inject the service into the auto-generated resolver.

sub-task/sub-task.module.ts
import { NestjsQueryGraphQLModule } from '@nestjs-query/query-graphql';import { Module } from '@nestjs/common';import { SubTaskDTO } from './dto/sub-task.dto';import { SubTaskEntity } from './sub-task.entity';import { TodoItemReferenceDTO } from './dto/todo-item-reference.dto';import { TodoItemService } from './todo-item.service';
@Module({  imports: [    NestjsQueryGraphQLModule.forFeature({      imports: [/* import the nestjs-query persistence module*/],      services: [TodoItemService],      resolvers: [        {          DTOClass: SubTaskDTO,          EntityClass: SubTaskEntity,        },        {          type: 'federated',          DTOClass: TodoItemReferenceDTO,          Service: TodoItemService,        },      ],    }),  ],})export class SubTaskModule {}

Complete Example#

To see a complete example checkout https://github.com/doug-martin/nestjs-query/tree/master/examples/federation