Skip to content

Commit 821c4f0

Browse files
author
Rick Dutour Geerling
committed
docs: rough draft code-first federation
Examples need to be tested and some extra text would be nice
1 parent 7f2f2f9 commit 821c4f0

File tree

1 file changed

+202
-10
lines changed

1 file changed

+202
-10
lines changed

content/graphql/federation.md

Lines changed: 202 additions & 10 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
@@ -38,20 +40,20 @@ Our resolver has one extra method: `resolveReference`. It's called by the Apollo
3840

3941
```typescript
4042
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
41-
import { UsersService } from './users.service';
43+
import { UserService } from './user.service';
4244

4345
@Resolver('User')
44-
export class UsersResolvers {
45-
constructor(private usersService: UsersService) {}
46+
export class UserResolver {
47+
constructor(private userService: UserService) {}
4648

4749
@Query()
4850
getUser(@Args('id') id: string) {
49-
return this.usersService.findById(id);
51+
return this.userService.findById(id);
5052
}
5153

5254
@ResolveReference()
5355
resolveReference(reference: { __typename: string; id: string }) {
54-
return this.usersService.findById(reference.id);
56+
return this.userService.findById(reference.id);
5557
}
5658
}
5759
```
@@ -61,21 +63,88 @@ Finally, we hook everything up in a module together with a `GraphQLFederationMod
6163
```typescript
6264
import { Module } from '@nestjs/common';
6365
import { GraphQLFederationModule } from '@nestjs/graphql';
64-
import { UsersResolvers } from './users.resolvers';
66+
import { UserResolver } from './user.resolver';
6567

6668
@Module({
6769
imports: [
6870
GraphQLFederationModule.forRoot({
6971
typePaths: ['**/*.graphql'],
7072
}),
7173
],
72-
providers: [UsersResolvers],
74+
providers: [UserResolver],
75+
})
76+
export class AppModule {}
77+
```
78+
79+
##### Code first
80+
81+
The code-first Federation is very similar to a 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 { UserService } from './user.service';
107+
108+
@Resolver(of => User)
109+
export class UserResolver {
110+
constructor(private userService: UserService) {}
111+
112+
@Query(returns => User)
113+
getUser(@Args('id') id: string) {
114+
return this.userService.findById(id);
115+
}
116+
117+
@ResolveReference()
118+
resolveReference(reference: { __typename: string; id: string }) {
119+
return this.userService.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 { UserResolver } from './user.resolver';
130+
131+
@Module({
132+
imports: [
133+
GraphQLFederationModule.forRoot({
134+
autoSchemaFile: true,
135+
}),
136+
],
137+
providers: [UserResolver],
73138
})
74139
export class AppModule {}
75140
```
76141

77142
#### Federated example: Posts
78143

144+
Our Post microservice serves aggregated posts via a `getPosts` query, but also extends our User type with `user.posts`
145+
146+
##### Schema first
147+
79148
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.
80149

81150
```graphql
@@ -99,7 +168,7 @@ extend type Query {
99168
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.
100169

101170
```typescript
102-
import { Query, Resolver, Parent, ResolveProperty } from '@nestjs/graphql';
171+
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
103172
import { PostsService } from './posts.service';
104173
import { Post } from './posts.interfaces';
105174

@@ -112,7 +181,7 @@ export class PostsResolvers {
112181
return this.postsService.findAll();
113182
}
114183

115-
@ResolveProperty('user')
184+
@ResolveField('user')
116185
getUser(@Parent() post: Post) {
117186
return { __typename: 'User', id: post.userId };
118187
}
@@ -137,11 +206,134 @@ import { PostsResolvers } from './posts.resolvers';
137206
export class AppModule {}
138207
```
139208

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

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

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:
336+
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:
145337

146338
```typescript
147339
import { Module } from '@nestjs/common';

0 commit comments

Comments
 (0)