Skip to content

Commit 5653d3c

Browse files
Merge pull request #1121 from nestjs/feat/mapped-types-generator
feat(): add mapped types, inheritance, generator
2 parents 86726c4 + 8898ed9 commit 5653d3c

File tree

12 files changed

+612
-172
lines changed

12 files changed

+612
-172
lines changed

content/graphql/cli-plugin.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
### CLI Plugin
2+
3+
> warning **Warning** This chapter applies only to the code first approach.
4+
5+
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.
6+
7+
> info **Hint** This plugin is **opt-in**. If you prefer, you can declare all decorators manually, or only specific decorators where you need them.
8+
9+
#### Overview
10+
11+
The GraphQL plugin will automatically:
12+
13+
- annotate all input object, object type and args classes properties with `@Field` unless `@HideField` is used
14+
- set the `nullable` property depending on the question mark (e.g. `name?: string` will set `nullable: true`)
15+
- set the `type` property depending on the type (supports arrays as well)
16+
17+
Please, note that your filenames **must have** one of the following suffixes in order to be analyzed by the plugin: `['.input.ts', '.args.ts', '.entity.ts', '.model.ts']` (e.g., `author.entity.ts`). If you are using a different suffix, you can adjust the plugin's behavior by specifying the `typeFileNameSuffix` option (see below).
18+
19+
With what we've learned so far, you have to duplicate a lot of code to let the package know how your type should be declared in GraphQL. For example, you could define a simple `Author` class as follows:
20+
21+
```typescript
22+
@@filename(authors/models/author.model)
23+
@ObjectType()
24+
export class Author {
25+
@Field(type => Int)
26+
id: number;
27+
28+
@Field({ nullable: true })
29+
firstName?: string;
30+
31+
@Field({ nullable: true })
32+
lastName?: string;
33+
34+
@Field(type => [Post])
35+
posts: Post[];
36+
}
37+
```
38+
39+
While not a significant issue with medium-sized projects, it becomes verbose & hard to maintain once you have a large set of classes.
40+
41+
By enabling the GraphQL plugin, the above class definition can be declared simply:
42+
43+
```typescript
44+
@@filename(authors/models/author.model)
45+
@ObjectType()
46+
export class Author {
47+
@Field(type => Int)
48+
id: number;
49+
firstName?: string;
50+
lastName?: string;
51+
posts: Post[];
52+
}
53+
```
54+
55+
The plugin adds appropriate decorators on-the-fly based on the **Abstract Syntax Tree**. Thus, you won't have to struggle with `@Field` decorators scattered throughout the code.
56+
57+
> info **Hint** The plugin will automatically generate any missing swagger properties, but if you need to override them, simply set them explicitly via `@Field()`.
58+
#### Using the CLI plugin
59+
To enable the plugin, open `nest-cli.json` (if you use [Nest CLI](/cli/overview)) and add the following `plugins` configuration:
60+
61+
```javascript
62+
{
63+
"collection": "@nestjs/schematics",
64+
"sourceRoot": "src",
65+
"compilerOptions": {
66+
"plugins": ["@nestjs/graphql/plugin"]
67+
}
68+
}
69+
```
70+
71+
You can use the `options` property to customize the behavior of the plugin.
72+
73+
```javascript
74+
"plugins": [
75+
{
76+
"name": "@nestjs/graphql/plugin",
77+
"options": {
78+
"typeFileNameSuffix": [".input.ts", ".args.ts"]
79+
}
80+
}
81+
]
82+
```
83+
84+
The `options` property has to fulfill the following interface:
85+
86+
```typescript
87+
export interface PluginOptions {
88+
typeFileNameSuffix?: string[];
89+
}
90+
```
91+
92+
<table>
93+
<tr>
94+
<th>Option</th>
95+
<th>Default</th>
96+
<th>Description</th>
97+
</tr>
98+
<tr>
99+
<td><code>typeFileNameSuffix</code></td>
100+
<td><code>['.input.ts', '.args.ts', '.entity.ts', '.model.ts']</code></td>
101+
<td>GraphQL types files suffix</td>
102+
</tr>
103+
</table>
104+
105+
If you don't use the CLI but instead have a custom `webpack` configuration, you can use this plugin in combination with `ts-loader`:
106+
107+
```javascript
108+
getCustomTransformers: (program: any) => ({
109+
before: [require('@nestjs/graphql/plugin').before({}, program)]
110+
}),
111+
```

content/graphql/mapped-types.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
### Mapped types
2+
3+
> warning **Warning** This chapter applies only to the code first approach.
4+
5+
As you build out features like CRUD (Create/Read/Update/Delete) it's often useful to construct variants on a base entity type. Nest provides several utility functions that perform type transformations to make this task more convenient.
6+
7+
#### Partial
8+
9+
When building input validation types (also called DTOs), it's often useful to build **create** and **update** variations on the same type. For example, the **create** variant may require all fields, while the **update** variant may make all fields optional.
10+
11+
Nest provides the `PartialType()` utility function to make this task easier and minimize boilerplate.
12+
13+
The `PartialType()` function returns a type (class) with all the properties of the input type set to optional. For example, suppose we have a **create** type as follows:
14+
15+
```typescript
16+
@InputType()
17+
class CreateUserInput {
18+
@Field()
19+
email: string;
20+
21+
@Field()
22+
password: string;
23+
24+
@Field()
25+
firstName: string;
26+
}
27+
```
28+
29+
By default, all of these fields are required. To create a type with the same fields, but with each one optional, use `PartialType()` passing the class reference (`CreateUserInput`) as an argument:
30+
31+
```typescript
32+
@InputType()
33+
export class UpdateUserInput extends PartialType(CreateUserInput) {}
34+
```
35+
36+
> info **Hint** The `PartialType()` function is imported from the `@nestjs/graphql` package.
37+
38+
The `PartialType()` function takes an optional second argument that is a reference to the decorator factory of the type being extended. In the example above, we are extending `CreateUserInput` which is annotated with the `@InputType()` decorator. We didn't need to pass `InputType` as the second argument since it's the default value. If you want to extend a class decorated with `@ObjectType`, pass `ObjectType` as the second argument. For example:
39+
40+
```typescript
41+
@InputType()
42+
export class UpdateUserInput extends PartialType(User, ObjectType) {}
43+
```
44+
45+
#### Pick
46+
47+
The `PickType()` function constructs a new type (class) by picking a set of properties from an input type. For example, suppose we start with a type like:
48+
49+
```typescript
50+
@InputType()
51+
class CreateUserInput {
52+
@Field()
53+
email: string;
54+
55+
@Field()
56+
password: string;
57+
58+
@Field()
59+
firstName: string;
60+
}
61+
```
62+
63+
We can pick a set of properties from this class using the `PickType()` utility function:
64+
65+
```typescript
66+
@InputType()
67+
export class UpdateEmailInput extends PickType(CreateUserInput, ['email']) {}
68+
```
69+
70+
> info **Hint** The `PickType()` function is imported from the `@nestjs/graphql` package.
71+
72+
#### Omit
73+
74+
The `OmitType()` function constructs a type by picking all properties from an input type and then removing a particular set of keys. For example, suppose we start with a type like:
75+
76+
```typescript
77+
@InputType()
78+
class CreateUserInput {
79+
@Field()
80+
email: string;
81+
82+
@Field()
83+
password: string;
84+
85+
@Field()
86+
firstName: string;
87+
}
88+
```
89+
90+
We can generate a derived type that has every property **except** `email` as shown below. In this construct, the second argument to `OmitType` is an array of property names.
91+
92+
```typescript
93+
@InputType()
94+
export class UpdateUserInput extends OmitType(CreateUserInput, ['email']) {}
95+
```
96+
97+
> info **Hint** The `OmitType()` function is imported from the `@nestjs/graphql` package.
98+
99+
#### Composition
100+
101+
The type mapping utility functions are composable. For example, the following will produce a type (class) that has all of the properties of the `CreateUserInput` type except for `email`, and those properties will be set to optional:
102+
103+
```typescript
104+
@InputType()
105+
export class UpdateUserInput extends PartialType(
106+
OmitType(CreateUserInput, ['email']),
107+
) {}
108+
```

content/graphql/mutations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class UpvotePostInput {
3939
}
4040
```
4141

42-
> info **Hint** The `@InputType()` decorator takes an options object as an argument, so you can, for example, specify the input type's description. Note that, due to TypeScript's metadata reflection system limitations, you must either use the `@Field` decorator to manually indicate a type, or use a [CLI plugin](/graphql/resolvers#cli-plugin).
42+
> info **Hint** The `@InputType()` decorator takes an options object as an argument, so you can, for example, specify the input type's description. Note that, due to TypeScript's metadata reflection system limitations, you must either use the `@Field` decorator to manually indicate a type, or use a [CLI plugin](/graphql/cli-plugin).
4343
4444
We can then use this type in the resolver class:
4545

content/graphql/quick-start.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ 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+
In some circumstances (for example end-to-end tests), you may want to get a reference to the generated schema object. In end-to-end tests, you can then run queries using the `graphql` object without using any HTTP listeners.
177+
178+
You can access the generated schema (in either the code first or schema first approach), using the `GraphQLSchemaHost` class:
179+
180+
```typescript
181+
const { schema } = app.get(GraphQLSchemaHost);
182+
```
183+
184+
> info **Hint** You must call the `GraphQLSchemaHost#schema` getter after the application has been initialized (after the `onModuleInit` hook has been triggered by either the `app.listen()` or `app.init()` method).
185+
174186
#### Async configuration
175187

176188
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.

0 commit comments

Comments
 (0)