Skip to content

Commit 7157e5f

Browse files
committed
Merge branch 'feat/mapped-types-generator' of https://github.com/nestjs/docs.nestjs.com into biundo/graphql-mapped-types
2 parents 02a8086 + fa6bfba commit 7157e5f

File tree

9 files changed

+319
-0
lines changed

9 files changed

+319
-0
lines changed

content/graphql/mapped-types.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
### Mapped types
2+
3+
##### This chapter applies only to code first approach
4+
5+
A common task is to take an existing type and make each of its properties optional. This happens often enough that NestJS provides a way to create new types based on old types — mapped types.
6+
7+
#### Partial
8+
9+
Make all properties of a type optional.
10+
11+
Original type:
12+
13+
```typescript
14+
@InputType()
15+
class CreateUserInput {
16+
@Field()
17+
email: string;
18+
19+
@Field()
20+
password: string;
21+
22+
@Field()
23+
firstName: string;
24+
}
25+
```
26+
27+
Mapped type:
28+
29+
```typescript
30+
@InputType()
31+
export class UpdateUserInput extends PartialType(CreateUserInput) {}
32+
```
33+
34+
> info **Hint** The `PartialType()` function is imported from the `@nestjs/graphql` package.
35+
36+
> warning **Warning** If you want to create an `InputType` based on the `ObjectType`, you must explicitly specify it in the function, as follows: `PartialType(CreateUserInput, InputType)` where `InputType` is a reference to a decorator factory.
37+
38+
#### Pick
39+
40+
Constructs a type by picking the set of properties from type.
41+
42+
Original type:
43+
44+
```typescript
45+
@InputType()
46+
class CreateUserInput {
47+
@Field()
48+
email: string;
49+
50+
@Field()
51+
password: string;
52+
53+
@Field()
54+
firstName: string;
55+
}
56+
```
57+
58+
Mapped type:
59+
60+
```typescript
61+
@InputType()
62+
export class UpdateEmailInput extends PickType(CreateUserInput, ['email']) {}
63+
```
64+
65+
> info **Hint** The `PickType()` function is imported from the `@nestjs/graphql` package.
66+
67+
#### Pick
68+
69+
Constructs a type by picking all properties from type and then removing particular set of keys.
70+
71+
Original type:
72+
73+
```typescript
74+
@InputType()
75+
class CreateUserInput {
76+
@Field()
77+
email: string;
78+
79+
@Field()
80+
password: string;
81+
82+
@Field()
83+
firstName: string;
84+
}
85+
```
86+
87+
Mapped type:
88+
89+
```typescript
90+
@InputType()
91+
export class UpdateUserInput extends OmitType(CreateUserInput, ['email']) {}
92+
```
93+
94+
> info **Hint** The `PickType()` function is imported from the `@nestjs/graphql` package.
95+
96+
#### Composition
97+
98+
Mapped types are composeable.
99+
100+
Example:
101+
102+
```typescript
103+
@InputType()
104+
export class UpdateUserInput extends Partial(
105+
OmitType(CreateUserInput, ['email']),
106+
) {}
107+
```

content/graphql/mutations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ async upvotePost(
5050
) {}
5151
```
5252

53+
// Should we add note about Inheritance here? (input types inheritance)
54+
5355
#### Schema first
5456

5557
Let's extend our `AuthorResolver` used in the previous section (see [resolvers](/graphql/resolvers)).

content/graphql/quick-start.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ definitionsFactory.generate({
171171

172172
A fully working schema first sample is available [here](https://github.com/nestjs/nest/tree/master/sample/12-graphql-schema-first).
173173

174+
#### Accessing generated schema
175+
176+
To access the generated schema (in either code first or schema first approach), use the `GraphQLHost` class:
177+
178+
```typescript
179+
const { schema } = app.get(GraphQLSchemaHost);
180+
```
181+
182+
> info **Hint** Make sure to call the `GraphQLSchemaHost#schema` getter when the application is already initialized (after the `onModuleInit` hook triggered by either `app.listen()` or `app.init()` method.
183+
174184
#### Async configuration
175185

176186
When you need to pass module options asynchronously instead of statically, use the `forRootAsync()` method. As with most dynamic modules, Nest provides several techniques to deal with async configuration.

content/graphql/resolvers-map.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,133 @@ type Query {
306306

307307
> info **Hint** Note that arguments classes like `GetAuthorArgs` play very well with the `ValidationPipe` (read [more](/techniques/validation)).
308308

309+
#### Class inheritance
310+
311+
Args:
312+
313+
```typescript
314+
@ArgsType()
315+
class PaginationArgs {
316+
@Field(type => Int)
317+
offset: number = 0;
318+
319+
@Field(type => Int)
320+
limit: number = 10;
321+
}
322+
```
323+
324+
```typescript
325+
@ArgsType()
326+
class GetAuthorArgs extends PaginationArgs {
327+
@Field({ nullable: true })
328+
firstName?: string;
329+
330+
@Field({ defaultValue: '' })
331+
@MinLength(3)
332+
lastName: string;
333+
}
334+
```
335+
336+
Object types:
337+
338+
```typescript
339+
@ObjectType()
340+
class Character {
341+
@Field(type => Int)
342+
id: number;
343+
344+
@Field()
345+
name: string;
346+
}
347+
```
348+
349+
```typescript
350+
@ObjectType()
351+
class Warrior extends Character {
352+
@Field()
353+
level: number;
354+
}
355+
```
356+
357+
Resolver inheritance (creating base resolver):
358+
359+
```typescript
360+
function BaseResolver<T extends Type<unknown>>(classRef: T): any {
361+
@Resolver({ isAbstract: true })
362+
abstract class BaseResolverHost {
363+
@Query(type => [classRef], { name: `findAll${classRef.name}` })
364+
async findAll(): Promise<T[]> {
365+
return [];
366+
}
367+
}
368+
return BaseResolverHost;
369+
}
370+
```
371+
372+
- explicit return type `any` is required: otherwise TypeScript complains about the usage of private class definition (recommended: define an interface instead of `any`)
373+
- `Type` is imported from the `@nestjs/common` package
374+
- `isAbstract: true` indicates that SDL shouldn't be generated for this class (you can set this for other types as well, e.g., object type)
375+
376+
```typescript
377+
@Resolver(of => Recipe)
378+
export class RecipesResolver extends BaseResolver(Recipe) {
379+
constructor(private recipesService: RecipesService) {
380+
super();
381+
}
382+
}
383+
```
384+
385+
Generated SDL:
386+
387+
```graphql
388+
type Query {
389+
findAllRecipe: [Recipe!]!
390+
}
391+
```
392+
393+
#### Generics
394+
395+
Cursor-based pagination example:
396+
397+
```typescript
398+
import { Field, ObjectType, Int } from '@nestjs/graphql';
399+
import { Type } from '@nestjs/common';
400+
401+
export function Paginated<T>(classRef: Type<T>) {
402+
@ObjectType(`${classRef.name}Edge`)
403+
abstract class EdgeType {
404+
@Field(type => String)
405+
cursor: string;
406+
407+
@Field(type => classRef)
408+
node: T;
409+
}
410+
411+
@ObjectType({ isAbstract: true })
412+
abstract class PaginatedType {
413+
@Field(type => [EdgeType], { nullable: true })
414+
edges: EdgeType[];
415+
416+
@Field(type => [classRef], { nullable: true })
417+
nodes: T[];
418+
419+
@Field(type => Int)
420+
totalCount: number;
421+
422+
@Field()
423+
hasNextPage: boolean;
424+
}
425+
return PaginatedType;
426+
}
427+
```
428+
429+
No reason to explain everything in detail. We can link this doc: https://graphql.org/learn/pagination/#pagination-and-edges
430+
431+
```typescript
432+
@ObjectType()
433+
class PaginatedAuthor extends Paginated(Author) {}
434+
```
435+
309436
#### Schema first
310437

311438
As mentioned in the [previous](/graphql/quick-start) chapter, in the schema first approach we start by manually defining schema types in SDL (read [more](http://graphql.org/learn/schema/#type-language)). Consider the following SDL type definitions.
@@ -527,6 +654,8 @@ export class AuthorsModule {}
527654

528655
> info **Hint** It is helpful to organize your code by your so-called **domain model** (similar to the way you would organize entry points in a REST API). In this approach, keep your models (`ObjectType` classes), resolvers and services together within a Nest module representing the domain model. Keep all of these components in a single folder per module. When you do this, and use the [Nest CLI](/cli/overview) to generate each element, Nest will wire all of these parts together (locating files in appropriate folders, generating entries in `provider` and `imports` arrays, etc.) automatically for you.
529656
657+
/////// MAYBE LET'S CREATE A SEPARATE CHAPTER(PAGE) FOR THIS? [CLI plugin]
658+
530659
#### CLI Plugin
531660

532661
TypeScript's metadata reflection system has several limitations which make it impossible to, for instance, determine what properties a class consists of or recognize whether a given property is optional or required. However, some of these constraints can be addressed at compilation time. Nest provides a plugin that enhances the TypeScript compilation process to reduce the amount of boilerplate code required.

content/graphql/schema-generator.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
### Generating SDL
2+
3+
##### This chapter applies only to code first approach
4+
5+
To manually generate (without running an application, connecting to the database, hooking up resolvers, etc.) a GraphQL SDL, use the `GraphQLSchemaBuilderModule`.
6+
7+
```typescript
8+
async function generateSchema() {
9+
const app = await NestFactory.create(GraphQLSchemaBuilderModule);
10+
await app.init();
11+
12+
const gqlSchemaFactory = app.get(GraphQLSchemaFactory);
13+
const schema = await gqlSchemaFactory.create([RecipesResolver]);
14+
console.log(printSchema(schema));
15+
}
16+
```
17+
18+
> info **Hint** The `GraphQLSchemaBuilderModule` and `GraphQLSchemaFactory` are imported the `@nestjs/graphql` package, while the `printSchema` function is imported from the `graphql` package.
19+
20+
Multiple resolvers:
21+
22+
```typescript
23+
const schema = await gqlSchemaFactory.create([
24+
RecipesResolver,
25+
AuthorsResolver,
26+
PostsResolvers,
27+
]);
28+
```
29+
30+
Passing options:
31+
32+
```typescript
33+
const schema = await gqlSchemaFactory.create([RecipesResolver], {
34+
skipCheck: true,
35+
orphanedTypes: [],
36+
});
37+
```

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export class MenuComponent implements OnInit {
113113
{ title: 'Interfaces', path: '/graphql/interfaces' },
114114
{ title: 'Unions', path: '/graphql/unions' },
115115
{ title: 'Enums', path: '/graphql/enums' },
116+
{ title: 'Mapped types', path: '/graphql/mapped-types' },
117+
{ title: 'Generating SDL', path: '/graphql/generating-sdl' },
116118
{
117119
title: 'Other features',
118120
path: '/graphql/tooling',

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { EnumsComponent } from './enums/enums.component';
77
import { FederationComponent } from './federation/federation.component';
88
import { GuardsInterceptorsComponent } from './guards-interceptors/guards-interceptors.component';
99
import { InterfacesComponent } from './interfaces/interfaces.component';
10+
import { MappedTypesComponent } from './mapped-types/mapped-types.component';
1011
import { MutationsComponent } from './mutations/mutations.component';
1112
import { PluginsComponent } from './plugins/plugins.component';
1213
import { QuickStartComponent } from './quick-start/quick-start.component';
1314
import { ResolversMapComponent } from './resolvers-map/resolvers-map.component';
1415
import { ScalarsComponent } from './scalars/scalars.component';
16+
import { SchemaGeneratorComponent } from './schema-generator/schema-generator.component';
1517
import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
1618
import { UnionsComponent } from './unions/unions.component';
1719

@@ -84,6 +86,16 @@ const routes: Routes = [
8486
component: InterfacesComponent,
8587
data: { title: 'GraphQL + TypeScript - Interfaces' },
8688
},
89+
{
90+
path: 'mapped-types',
91+
component: MappedTypesComponent,
92+
data: { title: 'GraphQL + TypeScript - Mapped types' },
93+
},
94+
{
95+
path: 'generating-sdl',
96+
component: SchemaGeneratorComponent,
97+
data: { title: 'GraphQL + TypeScript - Generating SDL' },
98+
},
8799
];
88100

89101
@NgModule({
@@ -99,6 +111,8 @@ const routes: Routes = [
99111
PluginsComponent,
100112
GuardsInterceptorsComponent,
101113
ScalarsComponent,
114+
SchemaGeneratorComponent,
115+
MappedTypesComponent,
102116
FederationComponent,
103117
],
104118
})
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-mapped-types',
6+
templateUrl: './mapped-types.component.html',
7+
changeDetection: ChangeDetectionStrategy.OnPush,
8+
})
9+
export class MappedTypesComponent extends BasePageComponent {}
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-schema-generator',
6+
templateUrl: './schema-generator.component.html',
7+
changeDetection: ChangeDetectionStrategy.OnPush,
8+
})
9+
export class SchemaGeneratorComponent extends BasePageComponent {}

0 commit comments

Comments
 (0)