Skip to content

Commit e1815e6

Browse files
Merge branch 'tuxmachine-feat/federation-docs'
2 parents 2a861b3 + 9329437 commit e1815e6

File tree

7 files changed

+251
-3
lines changed

7 files changed

+251
-3
lines changed

content/graphql/federation.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
### Federation
2+
3+
[Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) offers a means of splitting your monolith GraphQL server into independent microservices. It consists of two components: A gateway and one or more federated microservices. Each microservice holds part of the schema and the gateway merges the schemas into one single schema that can be consumed by the client.
4+
5+
To quote the [Apollo docs](https://blog.apollographql.com/apollo-federation-f260cf525d21), Federation is designed with these core principles:
6+
7+
- Building a graph should be **declarative.** With federation, you compose a graph declaratively from within your schema instead of writing imperative schema stitching code.
8+
- Code should be separated by **concern**, not by types. Often no single team controls every aspect of an important type like a User or Product, so the definition of these types should be distributed across teams and codebases, rather than centralized.
9+
- The graph should be simple for clients to consume. Together, federated services can form a complete, product-focused graph that accurately reflects how it’s being consumed on the client.
10+
- It’s just **GraphQL**, using only spec-compliant features of the language. Any language, not just JavaScript, can implement federation.
11+
12+
> warning **Note** Apollo Federation currently does not support subscriptions, and only the schema-first approach is currently supported due to limitations with the decorators not yet supporting GraphQL directives<sup>[1](https://github.com/MichalLytek/type-graphql/issues/351)</sup>
13+
14+
In the next example, we'll set up a demo application with a gateway and two federated endpoints: a user- and posts service.
15+
16+
#### Federated example: Users
17+
18+
First install the optional dependency for federation:
19+
20+
```bash
21+
$ npm install --save @apollo/federation
22+
```
23+
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.
25+
26+
```graphql
27+
type User @key(fields: "id") {
28+
id: ID!
29+
name: String!
30+
}
31+
32+
extend type Query {
33+
getUser(id: ID!): User
34+
}
35+
```
36+
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.
38+
39+
```typescript
40+
import { Args, Query, Resolver, ResolveReference } from '@nestjs/graphql';
41+
import { UsersService } from './users.service';
42+
43+
@Resolver('User')
44+
export class UsersResolvers {
45+
constructor(private readonly usersService: UsersService) {}
46+
47+
@Query()
48+
getUser(@Args('id') id: string) {
49+
return this.usersService.findById(id);
50+
}
51+
52+
@ResolveReference()
53+
resolveReference(reference: { __typename: string; id: string }) {
54+
return this.usersService.findById(reference.id);
55+
}
56+
}
57+
```
58+
59+
Finally, we hook everything up in a module together with a `GraphQLFederationModule`. This module accepts the same options as the regular `GraphQLModule`.
60+
61+
```typescript
62+
import { Module } from '@nestjs/common';
63+
import { GraphQLFederationModule } from '@nestjs/graphql';
64+
import { UsersResolvers } from './users.resolvers';
65+
66+
@Module({
67+
imports: [
68+
GraphQLFederationModule.forRoot({
69+
typePaths: ['**/*.graphql'],
70+
}),
71+
],
72+
providers: [UsersResolvers],
73+
})
74+
export class AppModule {}
75+
```
76+
77+
#### Federated example: Posts
78+
79+
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.
80+
81+
```graphql
82+
type Post @key(fields: "id") {
83+
id: ID!
84+
title: String!
85+
body: String!
86+
user: User
87+
}
88+
89+
extend type User @key(fields: "id") {
90+
id: ID! @external
91+
posts: [Post]
92+
}
93+
94+
extend type Query {
95+
getPosts: [Post]
96+
}
97+
```
98+
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 User service discussed above will be called on the `resolveReference` method.
100+
101+
```typescript
102+
import { Query, Resolver, Parent, ResolveProperty } from '@nestjs/graphql';
103+
import { PostsService } from './posts.service';
104+
import { Post } from './posts.interfaces';
105+
106+
@Resolver('Post')
107+
export class PostsResolvers {
108+
constructor(private readonly postsService: PostsService) {}
109+
110+
@Query('getPosts')
111+
getPosts() {
112+
return this.postsService.findAll();
113+
}
114+
115+
@ResolveProperty('user')
116+
getUser(@Parent() post: Post) {
117+
return { __typename: 'User', id: post.userId };
118+
}
119+
}
120+
```
121+
122+
The Posts service has virtually the same module, but is included below for the sake of completeness:
123+
124+
```typescript
125+
import { Module } from '@nestjs/common';
126+
import { GraphQLFederationModule } from '@nestjs/graphql';
127+
import { PostsResolvers } from './posts.resolvers';
128+
129+
@Module({
130+
imports: [
131+
GraphQLFederationModule.forRoot({
132+
typePaths: ['**/*.graphql'],
133+
}),
134+
],
135+
providers: [PostsResolvers],
136+
})
137+
export class AppModule {}
138+
```
139+
140+
#### Federated example: Gateway
141+
142+
First install the optional dependency for the gateway: `npm install --save @apollo/gateway`.
143+
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:
145+
146+
```typescript
147+
import { Module } from '@nestjs/common';
148+
import { GraphQLGatewayModule } from '@nestjs/graphql';
149+
150+
@Module({
151+
imports: [
152+
GraphQLGatewayModule.forRoot({
153+
server: {
154+
// ... Apollo server options
155+
cors: true,
156+
},
157+
gateway: {
158+
serviceList: [
159+
{ name: 'users', url: 'http://user-service/graphql' },
160+
{ name: 'posts', url: 'http://post-service/graphql' },
161+
],
162+
},
163+
}),
164+
],
165+
})
166+
export class AppModule {}
167+
```
168+
169+
> info **Hint** Apollo recommends that you don't rely on the service discovery in a production environment but use their [Graph Manager](https://www.apollographql.com/docs/graph-manager/federation/) instead.
170+
171+
#### Sharing context
172+
173+
You can customize the requests between the gateway and federated services using a build service. This allows you to share context about the request. You can easily extend the default `RemoteGraphQLDataSource` and implement one of the hooks. Please refer to [Apollo Docs](https://www.apollographql.com/docs/apollo-server/api/apollo-gateway/#remotegraphqldatasource) on `RemoteGraphQLDataSource` for more information about the possibilities.
174+
175+
```typescript
176+
import { Module } from '@nestjs/common';
177+
import { GATEWAY_BUILD_SERVICE, GraphQLGatewayModule } from '@nestjs/graphql';
178+
import { RemoteGraphQLDataSource } from '@apollo/gateway';
179+
import { decode } from 'jsonwebtoken';
180+
181+
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
182+
async willSendRequest({ request, context }) {
183+
const { userId } = await decode(context.jwt);
184+
request.http.headers.set('x-user-id', userId);
185+
}
186+
}
187+
188+
@Module({
189+
providers: [
190+
{
191+
provide: AuthenticatedDataSource,
192+
useValue: AuthenticatedDataSource,
193+
},
194+
{
195+
provide: GATEWAY_BUILD_SERVICE,
196+
useFactory: AuthenticatedDataSource => {
197+
return ({ name, url }) => new AuthenticatedDataSource({ url });
198+
},
199+
inject: [AuthenticatedDataSource],
200+
},
201+
],
202+
exports: [GATEWAY_BUILD_SERVICE],
203+
})
204+
class BuildServiceModule {}
205+
206+
@Module({
207+
imports: [
208+
GraphQLGatewayModule.forRootAsync({
209+
useFactory: async () => ({
210+
gateway: {
211+
serviceList: [
212+
/* services */
213+
],
214+
},
215+
server: {
216+
context: ({ req }) => ({
217+
jwt: req.headers.authorization,
218+
}),
219+
},
220+
}),
221+
imports: [BuildServiceModule],
222+
inject: [GATEWAY_BUILD_SERVICE],
223+
}),
224+
],
225+
})
226+
export class AppModule {}
227+
```
228+
229+
#### Async configuration
230+
231+
Both the Federation and Gateway modules support asynchronous initialization using the same `forRootAsync` that's documented in [Quick start](/graphql/quick-start#async-configuration).

content/graphql/scalars.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ GraphQL includes the following default types: `Int`, `Float`, `String`, `Boolean
66

77
To define a custom scalar (read more about scalars [here](https://www.apollographql.com/docs/graphql-tools/scalars.html)), create a type definition and a dedicated resolver. Here (as in the official documentation), we’ll use the `graphql-type-json` package for demonstration purposes. This npm package defines a `JSON` GraphQL scalar type.
88

9-
Start by install the package:
9+
Start by installing the package:
1010

1111
```bash
1212
$ npm i --save graphql-type-json

content/graphql/subscriptions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ type Subscription {
127127
}
128128
```
129129

130-
Wit this, we've created a single `commentAdded(repoFullName: String!): Comment` subscription. You can find a full sample implementation [here](https://github.com/nestjs/nest/blob/master/sample/12-graphql-apollo).
130+
With this, we've created a single `commentAdded(repoFullName: String!): Comment` subscription. You can find a full sample implementation [here](https://github.com/nestjs/nest/blob/master/sample/12-graphql-apollo).
131131

132132
#### Code first
133133

content/techniques/security.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ In this chapter we cover various techniques that help you to increase the securi
44

55
#### Helmet
66

7-
[Helmet](https://github.com/helmetjs/helmet) can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately. Generally, Helmet is just a collection of 12 smaller middleware functions that set security-related HTTP headers (read [more](https://github.com/helmetjs/helmet#how-it-works)).
7+
[Helmet](https://github.com/helmetjs/helmet) can help protect your app from some well-known web vulnerabilities by setting HTTP headers appropriately. Generally, Helmet is just a collection of 14 smaller middleware functions that set security-related HTTP headers (read [more](https://github.com/helmetjs/helmet#how-it-works)).
88

99
Start by installing the required package:
1010

src/app/homepage/menu/menu.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export class MenuComponent implements OnInit {
112112
title: 'Other features',
113113
path: '/graphql/tooling',
114114
},
115+
{ title: 'Federation', path: '/graphql/federation' },
115116
],
116117
},
117118
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ChangeDetectionStrategy, Component } from '@angular/core';
2+
import { BasePageComponent } from '../../page/page.component';
3+
4+
@Component({
5+
selector: 'app-federation',
6+
templateUrl: './federation.component.html',
7+
changeDetection: ChangeDetectionStrategy.OnPush,
8+
})
9+
export class FederationComponent extends BasePageComponent {}

src/app/homepage/pages/graphql/graphql.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ResolversMapComponent } from './resolvers-map/resolvers-map.component';
99
import { ScalarsComponent } from './scalars/scalars.component';
1010
import { SchemaStitchingComponent } from './schema-stitching/schema-stitching.component';
1111
import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
12+
import { FederationComponent } from './federation/federation.component';
1213

1314
const routes: Routes = [
1415
{
@@ -50,6 +51,11 @@ const routes: Routes = [
5051
component: SchemaStitchingComponent,
5152
data: { title: 'GraphQL - Schema Stitching' },
5253
},
54+
{
55+
path: 'federation',
56+
component: FederationComponent,
57+
data: { title: 'GraphQL - Federation' },
58+
},
5359
];
5460

5561
@NgModule({
@@ -62,6 +68,7 @@ const routes: Routes = [
6268
SchemaStitchingComponent,
6369
GuardsInterceptorsComponent,
6470
ScalarsComponent,
71+
FederationComponent,
6572
],
6673
})
6774
export class GraphqlModule {}

0 commit comments

Comments
 (0)