Skip to content

Commit 4bc5c8a

Browse files
Merge branch 'tuxmachine-feature/code-first-federation'
2 parents 021561e + e196889 commit 4bc5c8a

File tree

1 file changed

+191
-9
lines changed

1 file changed

+191
-9
lines changed

content/graphql/federation.md

Lines changed: 191 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ In the next example, we'll set up a demo application with a gateway and two fede
1515

1616
#### Federated example: Users
1717

18-
First install the optional dependency for federation:
18+
First, install the optional dependency for federation:
1919

2020
```bash
2121
$ npm install --save @apollo/federation
2222
```
2323

24-
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.
24+
#### Schema first
25+
26+
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
2729
type User @key(fields: "id") {
@@ -34,7 +36,7 @@ extend type Query {
3436
}
3537
```
3638

37-
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.
39+
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.
3840

3941
```typescript
4042
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
@@ -74,8 +76,72 @@ 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+
id: number;
91+
92+
@Field()
93+
name: string;
94+
}
95+
```
96+
97+
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.
98+
99+
```ts
100+
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
101+
import { User } from './user.entity';
102+
import { UsersService } from './users.service';
103+
104+
@Resolver((of) => User)
105+
export class UsersResolvers {
106+
constructor(private usersService: UsersService) {}
107+
108+
@Query((returns) => User)
109+
getUser(@Args('id') id: number): User {
110+
return this.usersService.findById(id);
111+
}
112+
113+
@ResolveReference()
114+
resolveReference(reference: { __typename: string; id: number }): User {
115+
return this.usersService.findById(reference.id);
116+
}
117+
}
118+
```
119+
120+
Finally, we hook everything up in a module together with a `GraphQLFederationModule`. This module accepts the same options as the regular `GraphQLModule`.
121+
122+
```typescript
123+
import { Module } from '@nestjs/common';
124+
import { GraphQLFederationModule } from '@nestjs/graphql';
125+
import { UsersResolvers } from './users.resolvers';
126+
import { UsersService } from './users.service'; // Not included in this example
127+
128+
@Module({
129+
imports: [
130+
GraphQLFederationModule.forRoot({
131+
autoSchemaFile: true,
132+
}),
133+
],
134+
providers: [UsersResolvers, UsersService],
135+
})
136+
export class AppModule {}
137+
```
138+
77139
#### Federated example: Posts
78140

141+
Our Post service serves aggregated posts via a `getPosts` query, but also extends our `User` type with `user.posts`
142+
143+
#### Schema first
144+
79145
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.
80146

81147
```graphql
@@ -96,10 +162,10 @@ extend type Query {
96162
}
97163
```
98164

99-
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.
165+
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.
100166

101167
```typescript
102-
import { Query, Resolver, Parent, ResolveProperty } from '@nestjs/graphql';
168+
import { Query, Resolver, Parent, ResolveField } from '@nestjs/graphql';
103169
import { PostsService } from './posts.service';
104170
import { Post } from './posts.interfaces';
105171

@@ -112,7 +178,7 @@ export class PostsResolvers {
112178
return this.postsService.findAll();
113179
}
114180

115-
@ResolveProperty('user')
181+
@ResolveField('user')
116182
getUser(@Parent() post: Post) {
117183
return { __typename: 'User', id: post.userId };
118184
}
@@ -137,11 +203,127 @@ import { PostsResolvers } from './posts.resolvers';
137203
export class AppModule {}
138204
```
139205

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

142-
First install the optional dependency for the gateway: `npm install --save @apollo/gateway`.
324+
First, install the optional dependency for the gateway: `npm install --save @apollo/gateway`.
143325

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:
326+
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:
145327

146328
```typescript
147329
import { Module } from '@nestjs/common';
@@ -193,7 +375,7 @@ class AuthenticatedDataSource extends RemoteGraphQLDataSource {
193375
},
194376
{
195377
provide: GATEWAY_BUILD_SERVICE,
196-
useFactory: AuthenticatedDataSource => {
378+
useFactory: (AuthenticatedDataSource) => {
197379
return ({ name, url }) => new AuthenticatedDataSource({ url });
198380
},
199381
inject: [AuthenticatedDataSource],

0 commit comments

Comments
 (0)