Skip to content

Commit 57dcdb4

Browse files
Merge branch 'feature/code-first-federation' of https://github.com/tuxmachine/docs.nestjs.com into tuxmachine-feature/code-first-federation
2 parents 021561e + 19795a8 commit 57dcdb4

File tree

1 file changed

+197
-3
lines changed

1 file changed

+197
-3
lines changed

content/graphql/federation.md

Lines changed: 197 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ First install the optional dependency for federation:
2121
$ npm install --save @apollo/federation
2222
```
2323

24+
##### Schema first
25+
2426
The User service has a simple schema. Note the `@key` directive: it tells the Apollo query planner that a particular instance of User can be fetched if you have its `id`. Also note that we extend the `Query` type.
2527

2628
```graphql
@@ -74,8 +76,76 @@ import { UsersResolvers } from './users.resolvers';
7476
export class AppModule {}
7577
```
7678

79+
##### Code first
80+
81+
Code first federation is very similar to regular code first GraphQL. We simply add some extra decorators to the `User` entity.
82+
83+
```ts
84+
import { Directive, Field, ID, ObjectType } from '@nestjs/graphql';
85+
86+
@ObjectType()
87+
@Directive('@key(fields: "id")')
88+
export class User {
89+
@Field(type => ID)
90+
public id: number;
91+
92+
@Field()
93+
public name: string;
94+
95+
constructor(user: Partial<User>) {
96+
Object.assign(this, user);
97+
}
98+
}
99+
```
100+
101+
Our resolver has one extra method: `resolveReference`. It's called by the Apollo Gateway whenever a related resource requires a `User` instance. We'll see an example of this in the Posts service later on. Please note the `@ResolveReference` decorator.
102+
103+
```ts
104+
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
105+
import { User } from './user.entity';
106+
import { UsersService } from './users.service';
107+
108+
@Resolver(of => User)
109+
export class UsersResolvers {
110+
constructor(private usersService: UsersService) {}
111+
112+
@Query(returns => User)
113+
getUser(@Args('id') id: number): User {
114+
return this.usersService.findById(id);
115+
}
116+
117+
@ResolveReference()
118+
resolveReference(reference: { __typename: string; id: number }): User {
119+
return this.usersService.findById(reference.id);
120+
}
121+
}
122+
```
123+
124+
Finally, we hook everything up in a module together with a `GraphQLFederationModule`. This module accepts the same options as the regular `GraphQLModule`.
125+
126+
```typescript
127+
import { Module } from '@nestjs/common';
128+
import { GraphQLFederationModule } from '@nestjs/graphql';
129+
import { UsersResolvers } from './users.resolvers';
130+
import { UsersService } from './users.service'; // Not included in this example
131+
132+
@Module({
133+
imports: [
134+
GraphQLFederationModule.forRoot({
135+
autoSchemaFile: true,
136+
}),
137+
],
138+
providers: [UsersResolvers, UsersService],
139+
})
140+
export class AppModule {}
141+
```
142+
77143
#### Federated example: Posts
78144

145+
Our Post service serves aggregated posts via a `getPosts` query, but also extends our `User` type with `user.posts`
146+
147+
##### Schema first
148+
79149
The Posts service references the User type in its schema by marking it with the `extend` keyword. It also adds one property to the User type. Note the `@key` directive used for matching instances of User, and the `@external` directive indicating that the `id` field is managed elsewhere.
80150

81151
```graphql
@@ -99,7 +169,7 @@ extend type Query {
99169
Our resolver has one method of interest here: `getUser`. It returns a reference containing `__typename` and any additional properties your application needs to resolve the reference, in this case only an `id`. The `__typename` is used by the GraphQL Gateway to pinpoint the microservice responsible for the User type and request the instance. The Users service discussed above will be called on the `resolveReference` method.
100170

101171
```typescript
102-
import { Query, Resolver, Parent, ResolveProperty } from '@nestjs/graphql';
172+
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
103173
import { PostsService } from './posts.service';
104174
import { Post } from './posts.interfaces';
105175

@@ -112,7 +182,7 @@ export class PostsResolvers {
112182
return this.postsService.findAll();
113183
}
114184

115-
@ResolveProperty('user')
185+
@ResolveField('user')
116186
getUser(@Parent() post: Post) {
117187
return { __typename: 'User', id: post.userId };
118188
}
@@ -137,11 +207,135 @@ import { PostsResolvers } from './posts.resolvers';
137207
export class AppModule {}
138208
```
139209

210+
##### Code first
211+
212+
We will need to create a class representing our `User` entity. Even though it lives in another service, we will be using and extending it. Note the `@extends` and `@external` directives.
213+
214+
```ts
215+
import { Directive, ObjectType, Field, ID } from '@nestjs/graphql';
216+
import { Post } from './post.entity';
217+
218+
@ObjectType()
219+
@Directive('@extends')
220+
@Directive('@key(fields: "id")')
221+
export class User {
222+
@Field(type => ID)
223+
@Directive('@external')
224+
public id: number;
225+
226+
@Field(type => [Post])
227+
public posts?: Post[];
228+
229+
constructor(user: Partial<User>) {
230+
Object.assign(this, user);
231+
}
232+
}
233+
```
234+
235+
We create the resolver for our extension on the `User` entity as follows:
236+
237+
```ts
238+
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
239+
import { PostsService } from './posts.service';
240+
import { Post } from './post.entity';
241+
import { User } from './user.entity';
242+
243+
@Resolver(of => User)
244+
export class UsersResolvers {
245+
constructor(private readonly postsService: PostsService) {}
246+
247+
@ResolveField(of => [Post])
248+
public posts(@Parent() user: User): Post[] {
249+
return this.postsService.forAuthor(user.id);
250+
}
251+
}
252+
```
253+
254+
We also need to create our `Post` entity:
255+
256+
```ts
257+
import { Directive, Field, ID, Int, ObjectType } from '@nestjs/graphql';
258+
import { User } from './user.entity';
259+
260+
@ObjectType()
261+
@Directive('@key(fields: "id")')
262+
export class Post {
263+
@Field(type => ID)
264+
public id: number;
265+
266+
@Field()
267+
public title: string;
268+
269+
@Field(type => Int)
270+
public authorId: number;
271+
272+
@Field(type => User)
273+
public user?: User;
274+
275+
constructor(post: Partial<Post>) {
276+
Object.assign(this, post);
277+
}
278+
}
279+
```
280+
281+
And its resolver:
282+
283+
```ts
284+
import { Query, Args, ResolveField, Resolver, Parent } from '@nestjs/graphql';
285+
import { PostsService } from './posts.service';
286+
import { Post } from './post.entity';
287+
import { User } from './user.entity';
288+
289+
@Resolver(of => Post)
290+
export class PostsResolvers {
291+
constructor(private readonly postsService: PostsService) {}
292+
293+
@Query(returns => Post)
294+
public findPost(@Args('id') id: number): Post {
295+
return this.postsService.findOne(id);
296+
}
297+
298+
@Query(returns => [Post])
299+
public getPosts(): Post[] {
300+
return this.postsService.all();
301+
}
302+
303+
@ResolveField(of => User)
304+
public user(@Parent() post: Post): any {
305+
return { __typename: 'User', id: post.authorId };
306+
}
307+
}
308+
```
309+
310+
And finally tie it together in a module. Note the schema build options, where we specify that `User` is an outside type.
311+
312+
```ts
313+
import { Module } from '@nestjs/common';
314+
import { GraphQLFederationModule } from '@nestjs/graphql';
315+
import { User } from './user.entity';
316+
import { PostsResolvers } from './posts.resolvers';
317+
import { UsersResolvers } from './users.resolvers';
318+
import { PostsService } from './posts.service'; // Not included in example
319+
320+
@Module({
321+
imports: [
322+
GraphQLFederationModule.forRoot({
323+
autoSchemaFile: true,
324+
buildSchemaOptions: {
325+
orphanedTypes: [User],
326+
},
327+
}),
328+
],
329+
providers: [PostsResolvers, UsersResolvers, PostsService],
330+
})
331+
export class AppModule {}
332+
```
333+
140334
#### Federated example: Gateway
141335

142336
First install the optional dependency for the gateway: `npm install --save @apollo/gateway`.
143337

144-
Our gateway only needs a list of endpoints and will auto-discover the schemas from there. The code for our gateway is therefore very short:
338+
Our gateway only needs a list of endpoints and will auto-discover the schemas from there. Therefore it is the same for code and schema first, and the code for our gateway is very short:
145339

146340
```typescript
147341
import { Module } from '@nestjs/common';

0 commit comments

Comments
 (0)