Skip to content

Commit 2123908

Browse files
committed
docs(graphql chapter): suggested updates
1 parent 3b589b5 commit 2123908

File tree

2 files changed

+89
-31
lines changed

2 files changed

+89
-31
lines changed

content/graphql/resolvers-map.md

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Resolvers provide the instructions for turning a [GraphQL](https://graphql.org/)
66

77
In the code first approach, we don't follow the typical process of creating our GraphQL schema by writing GraphQL SDL by hand. Instead, we use TypeScript decorators to generate the SDL from TypeScript class definitions. The `@nestjs/graphql` package reads the metadata defined through the decorators and automatically generates the schema for you.
88

9+
#### Object types
10+
911
Most of the definitions in a GraphQL schema are **object types**. Each object type you define should represent a domain object that an application client might need to interact with. For example, our sample API needs to be able to fetch a list of authors and their posts, so we should define the `Author` type and `Post` type to support this functionality.
1012

1113
If we were using the schema first approach, we'd define such a schema with SDL like this:
@@ -42,9 +44,9 @@ export class Author {
4244
}
4345
```
4446

45-
> info **Hint** 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. Because of these limitations, we must either explicitly use the `@Field` decorator in our schema definition classes, or use a [CLI plugin](/graphql/resolvers#cli-plugin) to generate these for us.
47+
> info **Hint** TypeScript's metadata reflection system has several limitations which make it impossible, for instance, to determine what properties a class consists of or recognize whether a given property is optional or required. Because of these limitations, we must either explicitly use the `@Field()` decorator in our schema definition classes to provide metadata about each field's GraphQL type and optionality, or use a [CLI plugin](/graphql/resolvers#cli-plugin) to generate these for us.
4648
47-
The `Author` object type, like any class, is made of a collection of fields, with each field declaring a type. The types correspond to [GraphQL types](https://graphql.org/learn/schema/). A field's GraphQL type can be either another object type or a scalar type. A GraphQL scalar type is a primitive (like `ID`, `String`, `Boolean`, or `Int`) that resolves to a single value.
49+
The `Author` object type, like any class, is made of a collection of fields, with each field declaring a type. A field's type corresponds to a [GraphQL type](https://graphql.org/learn/schema/). A field's GraphQL type can be either another object type or a scalar type. A GraphQL scalar type is a primitive (like `ID`, `String`, `Boolean`, or `Int`) that resolves to a single value.
4850

4951
> info **Hint** In addition to GraphQL's built-in scalar types, you can define custom scalar types (read [more](/graphql/scalars)).
5052
@@ -61,7 +63,7 @@ type Author {
6163

6264
The `@Field()` decorator accepts an optional type function (e.g., `type => Int`), and optionally an options object.
6365

64-
The type function is required when there's the potential for ambiguity between the TypeScript type system and the GraphQL type system. Specifically: it is **not** required for `string` and `boolean` types; it **is** required for arrays, numbers (which must be mapped to either an `Int` or a `Float`) and object types. The type function should simply return the desired GraphQL type (as shown in various examples in these chapters).
66+
The type function is required when there's the potential for ambiguity between the TypeScript type system and the GraphQL type system. Specifically: it is **not** required for `string` and `boolean` types; it **is** required for arrays, numbers (which must be mapped to either a GraphQL `Int` or `Float`) and object types. The type function should simply return the desired GraphQL type (as shown in various examples in these chapters).
6567

6668
The options object can have any of the following key/value pairs:
6769

@@ -78,14 +80,14 @@ title: string;
7880

7981
> info **Hint** You can also add a description to, or deprecate, the whole object type: `@ObjectType({{ '{' }} description: 'Author model' {{ '}' }})`.
8082
81-
When the field is an array, we must manually indicate the array type in the decorator type function, as shown below:
83+
When the field is an array, we must manually indicate the array type in the `Field()` decorator's type function, as shown below:
8284

8385
```typescript
8486
@Field(type => [Post])
8587
posts: Post[];
8688
```
8789

88-
> info **Hint** With `[ ]`, we can indicate the depth of the array. For example, using `[[Int]]` would represent an integer matrix.
90+
> info **Hint** Using array bracket notation (`[ ]`), we can indicate the depth of the array. For example, using `[[Int]]` would represent an integer matrix.
8991
9092
To declare that an array's items (not the array itself) are nullable, set the `nullable` property to `'items'` as shown below:
9193

@@ -128,7 +130,7 @@ type Post {
128130

129131
### Code first resolver
130132

131-
At this point, we've defined the objects (type definitions) that can exist in our data graph, but clients don't yet have a way to interact with those objects. To address that, we need to define a resolver class. In the code first method, defining a resolver class both defines resolver functions **and** generates the **Query type**. This will be clear as we work through the example below:
133+
At this point, we've defined the objects (type definitions) that can exist in our data graph, but clients don't yet have a way to interact with those objects. To address that, we need to create a resolver class. In the code first method, a resolver class both defines resolver functions **and** generates the **Query type**. This will be clear as we work through the example below:
132134

133135
```typescript
134136
@@filename(authors/authors.resolver)
@@ -140,7 +142,7 @@ export class AuthorsResolver {
140142
) {}
141143

142144
@Query(returns => Author)
143-
async author(@Args({ name: 'id', type: () => Int }) id: number) {
145+
async author(@Args('id', { type: () => Int }) id: number) {
144146
return this.authorsService.findOneById(id);
145147
}
146148

@@ -154,21 +156,27 @@ export class AuthorsResolver {
154156

155157
> info **Hint** All decorators (e.g., `@Resolver`, `@ResolveField`, `@Args`, etc.) are exported from the `@nestjs/graphql` package.
156158
157-
> warning **Warning** The logic inside the `AuthorsService` and `PostsService` classes can be as simple or sophisticated as needed. The main point of this example is to show how to construct resolvers and how they can interact with other providers.
159+
You can define multiple resolver classes. Nest will combine these at run time. See the [module](/graphql/resolvers#module) section below for more on code organization.
158160

159-
In the example above, we created the `AuthorsResolver` which defines one query resolver function and one field resolver function. To create a resolver, we create a class with resolver functions as methods, and we annotate the class with the `@Resolver()` decorator.
161+
> warning **Note** The logic inside the `AuthorsService` and `PostsService` classes can be as simple or sophisticated as needed. The main point of this example is to show how to construct resolvers and how they can interact with other providers.
160162
161-
The argument passed to the `@Resolver()` decorator is optional. However, in our case, since the class includes a **field resolver** function (for the `posts` property of the `Author` object type), it becomes required; in that case, the value is used to indicate which class is the parent type (i.e., the corresponding `ObjectType` class name) for any field resolver defined within this class.
163+
In the example above, we created the `AuthorsResolver` which defines one query resolver function and one field resolver function. To create a resolver, we create a class with resolver functions as methods, and annotate the class with the `@Resolver()` decorator.
162164

163165
In this example, we defined a query handler to get the author object based on the `id` sent in the request. To specify that the method is a query handler, use the `@Query()` decorator.
164166

167+
The argument passed to the `@Resolver()` decorator is optional, but comes into play when our graph becomes non-trivial. It's used to supply a parent object used by field resolver functions as they traverse down through an object graph.
168+
169+
In our example, since the class includes a **field resolver** function (for the `posts` property of the `Author` object type), we **must** supply the `@Resolver()` decorator with a value to indicate which class is the parent type (i.e., the corresponding `ObjectType` class name) for all field resolvers defined within this class. As should be clear from the example, when writing a field resolver function, it's necessary to access the parent object (the object the field being resolved is a member of). In this example, we populate an author's posts array with a field resolver that calls a service which takes the author's `id` as an argument. Hence the need to identify the parent object in the `@Resolver()` decorator. Note the corresponding use of the `@Parent()` method parameter decorator to then extract a reference to that parent object in the field resolver.
170+
165171
We can define multiple `@Query()` resolver functions (both within this class, and in any other resolver class), and they will be aggregated into a single **Query type** definition in the generated SDL along with the appropriate entries in the resolver map. This allows you to define queries close to the models and services that they use, and to keep them well organized in modules.
166172

173+
#### Query type names
174+
167175
In the above examples, the `@Query()` decorator generates a GraphQL schema query type name based on the method name. For example, consider the following construction from the example above:
168176

169177
```typescript
170178
@Query(returns => Author)
171-
async author(@Args({ name: 'id', type: () => Int }) id: number) {
179+
async author(@Args('id', { type: () => Int }) id: number) {
172180
return this.authorsService.findOneById(id);
173181
}
174182
```
@@ -195,7 +203,7 @@ export class AuthorsResolver {
195203
) {}
196204

197205
@Query(returns => Author, { name: 'author' })
198-
async getAuthor(@Args({ name: 'id', type: () => Int }) id: number) {
206+
async getAuthor(@Args('id', {type: () => Int }) id: number) {
199207
return this.authorsService.findOneById(id);
200208
}
201209

@@ -215,31 +223,52 @@ type Query {
215223
}
216224
```
217225

226+
#### Query decorator options
227+
218228
The `@Query()` decorator's options object (where we pass `{{ '{' }}name: 'author'{{ '}' }}` above) accepts a number of key/value pairs:
219229

220230
- `name`: name of the query; a `string`
221231
- `description`: a description that will be used to generate GraphQL schema documentation (e.g., in GraphQL playground); a `string`
222232
- `deprecationReason`: sets query metadata to show the query as deprecated (e.g., in GraphQL playground); a `string`
223233
- `nullable`: whether the query can return a null data response; `boolean` or `'items'` or `'itemsAndList'` (see above for details of `'items'` and `'itemsAndList'`)
224234

225-
Usually your `@Args()` decorator will be simple, and not require an object argument as seen with the `getAuthor()` method above. For example, if an identifier's type is string, the following construction is sufficient:
235+
### Args decorator options
236+
237+
Use the `@Args()` decorator to extract arguments from a request for use in the method handler. This works in a very similar fashion to [REST route parameter argument extraction](/controllers#route-parameters).
238+
239+
Usually your `@Args()` decorator will be simple, and not require an object argument as seen with the `getAuthor()` method above. For example, if an identifier's type is string, the following construction is sufficient, and simply plucks the named field from the inbound GraphQL request for use as a method argument.
226240

227241
```typescript
228242
@Args('id') id: string
229243
```
230244

231-
However, in this case, the `number` type is used, and it presents a challenge. The `number` TypeScript type doesn't give us enough information about the expected GraphQL representation (e.g., `Int` vs. `Float`). Thus we have to **explicitly** pass the type reference.
245+
In the `getAuthor()` case, the `number` type is used, which presents a challenge. The `number` TypeScript type doesn't give us enough information about the expected GraphQL representation (e.g., `Int` vs. `Float`). Thus we have to **explicitly** pass the type reference. We do that by passing a second argument to the `Args()` decorator, containing argument options, as shown below:
246+
247+
```typescript
248+
@Query(returns => Author, { name: 'author' })
249+
async getAuthor(@Args('id', { type: () => Int }) id: number) {
250+
return this.authorsService.findOneById(id);
251+
}
252+
```
253+
254+
The options object allows us to specify the following optional key value pairs:
255+
256+
- `type`: a function returning the GraphQL type
257+
- `defaultValue`: a default value; `any`
258+
- `description`: description metadata; `string`
259+
- `deprecationReason`: to deprecate a field and provide meta data describing why; `string`
260+
- `nullable`: whether the field is nullable
232261

233262
Query handler methods can take multiple arguments. Let's imagine that we want to fetch an author based on its `firstName` and `lastName`. In this case, we can call `@Args` twice:
234263

235264
```typescript
236265
getAuthor(
237-
@Args('firstName', { nullable: true }) firstName?: string,
238-
@Args('lastName', { defaultValue: '' }) lastName?: string,
266+
@Args( 'firstName', { nullable: true }) firstName?: string,
267+
@Args( 'lastName', { defaultValue: '' }) lastName?: string,
239268
) {}
240269
```
241270

242-
> info **Hint** Note that we can pass a second argument to the `@Args()` decorator, which is an options object. The options object allows us to specify a default value (`'defaultValue'` key), description (`'description'` key, ), deprecation reason (`'deprecationReason'` key), or if a field is nullable (`'nullable'` key).
271+
#### Dedicated arguments class
243272

244273
With inline `@Args()` calls, code like the example above becomes bloated. Instead, you can create a dedicated `GetAuthorArgs` arguments class and access it in the handler method as follows:
245274

@@ -265,7 +294,7 @@ class GetAuthorArgs {
265294
}
266295
```
267296

268-
> info **Hint** Again, due to TypeScript's metadata reflection system limitations, it's required to either use the `@Field` decorator to manually indicate a type, or use a [CLI plugin](/graphql/resolvers#cli-plugin).
297+
> info **Hint** Again, due to TypeScript's metadata reflection system limitations, it's required to either use the `@Field` decorator to manually indicate type and optionality, or use a [CLI plugin](/graphql/resolvers#cli-plugin).
269298
270299
This will result in generating the following part of the GraphQL schema in SDL:
271300

@@ -334,7 +363,7 @@ export class AuthorsResolver {
334363

335364
> info **Hint** All decorators (e.g., `@Resolver`, `@ResolveField`, `@Args`, etc.) are exported from the `@nestjs/graphql` package.
336365
337-
> warning **Warning** The logic inside the `AuthorsService` and `PostsService` classes can be as simple or sophisticated as needed. The main point of this example is to show how to construct resolvers and how they can interact with other providers.
366+
> warning **Note** The logic inside the `AuthorsService` and `PostsService` classes can be as simple or sophisticated as needed. The main point of this example is to show how to construct resolvers and how they can interact with other providers.
338367
339368
The `@Resolver()` decorator is required. It takes an optional string argument with the name of a class. This class name is required whenever the class includes `@ResolveField()` decorators to inform Nest that the decorated method is associated with a parent type (the `Author` type in our current example). Alternatively, instead of setting `@Resolver()` at the top of the class, this can be done for each method:
340369

0 commit comments

Comments
 (0)