解析器映射
解析器映射
解析器提供了将GraphQL操作(查询、变更或订阅)转换为数据的指令。它们返回与模式中描述的形状相同的数据,可以是同步的,也可以是异步的。通常,解析器返回一个Promise(例如,从数据库获取数据)。
代码优先
在代码优先方法中,我们不手动编写GraphQL模式。相反,我们使用装饰器从TypeScript类定义中自动生成模式。要创建解析器,我们将创建一个带有解析器方法的类,并用@Resolver()装饰器装饰该类。
在本章中,我们将使用一个简化的Recipe模型:
export class Recipe {
id: string;
title: string;
description?: string;
creationDate: Date;
ingredients: string[];
}
现在,让我们创建一个解析器类:
@@filename(recipes.resolver)
import { Args, Query, Resolver } from '@nestjs/graphql';
import { Recipe } from './models/recipe.model';
import { RecipeService } from './recipe.service';
@Resolver()
export class RecipeResolver {
constructor(private recipeService: RecipeService) {}
@Query(returns => [Recipe])
async recipes(): Promise<Recipe[]> {
return this.recipeService.findAll();
}
@Query(returns => Recipe)
async recipe(@Args('id') id: string): Promise<Recipe> {
return this.recipeService.findOneById(id);
}
}
提示 我们假设您已经创建了一个RecipeService(一个简单的CRUD服务,我们在这里不会详细介绍)。
在上面的示例中,我们定义了一个RecipeResolver,它提供了两个查询:recipes()和recipe()。要指定方法是查询处理程序,我们使用@Query()装饰器。类似地,要创建变更,我们将使用@Mutation()装饰器。要创建订阅,我们将使用@Subscription()装饰器。
装饰器的第一个参数是返回类型。请注意,由于TypeScript的元数据反射系统的限制,我们必须显式指定返回类型。例如,如果我们返回一个数组,我们必须手动指示数组项类型,如上面的recipes()方法所示。
要指定返回类型,我们使用类型引用(例如,Recipe)。
现在我们需要将Recipe模型转换为GraphQL模式。为此,我们用@ObjectType()装饰器注释模型类,并用@Field()装饰器装饰属性:
@@filename(models/recipe.model)
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class Recipe {
@Field()
id: string;
@Field()
title: string;
@Field({ nullable: true })
description?: string;
@Field()
creationDate: Date;
@Field(type => [String])
ingredients: string[];
}
提示 TypeScript的元数据反射系统只能确定类型是否为数组。因此,对于数组,我们必须手动指示数组项类型(如上面的ingredients字段所示)或使用显式类型引用。
现在Recipe模型已经注释,让我们重新访问RecipeResolver类。
@@filename(recipes.resolver)
import { Args, Query, Resolver } from '@nestjs/graphql';
import { Recipe } from './models/recipe.model';
import { RecipeService } from './recipe.service';
@Resolver(of => Recipe)
export class RecipeResolver {
constructor(private recipeService: RecipeService) {}
@Query(returns => [Recipe])
async recipes(): Promise<Recipe[]> {
return this.recipeService.findAll();
}
@Query(returns => Recipe)
async recipe(@Args('id') id: string): Promise<Recipe> {
return this.recipeService.findOneById(id);
}
}
请注意,我们将类引用(Recipe)传递给@Resolver()装饰器。这是可选的,但提供此信息允许库访问有用的元数据。
通常,我们使用专用的参数装饰器,如@Args()来提取GraphQL请求中的参数。
@Args('id') id: string
@Args({ name: 'id', type: () => Int }) id: number
在第一种情况下,我们依赖TypeScript的类型反射来推断参数类型。这适用于string和boolean等基本类型,但对于复杂类型(如数组或自定义GraphQL标量),我们必须显式传递类型引用。
或者,我们可以传递一个选项对象而不是参数名称:
@Query(returns => Recipe)
async recipe(@Args({ name: 'id', type: () => Int }) id: number): Promise<Recipe> {
return this.recipeService.findOneById(id);
}
@Args()装饰器的选项对象允许以下可选参数:
nullable: boolean- 参数是否可选defaultValue: any- 默认值description: string- 描述数据deprecationReason: string- 将字段标记为已弃用,并提供原因type: () => GraphQLScalarType- 显式GraphQL类型
最后,我们需要在模块中注册RecipeResolver:
@@filename(recipe.module)
import { Module } from '@nestjs/common';
import { RecipeResolver } from './recipe.resolver';
import { RecipeService } from './recipe.service';
@Module({
providers: [RecipeResolver, RecipeService],
})
export class RecipeModule {}
将所有内容放在一起,GraphQLModule将查找所有用@Resolver()装饰器注释的类,并将为每个方法处理程序自动生成查询映射。
对象类型
我们在上面定义的大多数装饰器都有一个可选的type函数。此类型函数返回GraphQL类型。我们已经看到了基本类型(String、Number、Boolean)和数组([String])的示例。但我们也可以指定复杂的对象类型。
例如,如果我们有一个Author对象类型:
@@filename(models/author.model)
import { Field, Int, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class Author {
@Field(type => Int)
id: number;
@Field({ nullable: true })
firstName?: string;
@Field({ nullable: true })
lastName?: string;
@Field(type => [Recipe])
recipes: Recipe[];
}
然后我们可以在我们的解析器中使用它:
@Query(returns => Author)
async author(@Args('id', { type: () => Int }) id: number): Promise<Author> {
return this.authorsService.findOneById(id);
}
参数
我们的解析器可以接受参数来访问来自客户端的数据。在上面的示例中,我们使用了@Args('id') id: string参数。这里我们使用@Args()装饰器从GraphQL请求中提取单个参数。
GraphQL请求可以接受多个参数。例如:
{
author(firstName: "John", lastName: "Doe") {
id
firstName
lastName
}
}
在这种情况下,我们可以使用以下方法签名:
@Query(returns => Author)
async author(
@Args('firstName') firstName: string,
@Args('lastName') lastName: string,
): Promise<Author> {
return this.authorsService.findOneByName(firstName, lastName);
}
专用参数类
使用内联@Args()调用,代码往往会变得臃肿。相反,您可以创建一个专用的GetAuthorArgs参数类:
@@filename(dto/get-author.args)
import { ArgsType, Field } from '@nestjs/graphql';
@ArgsType()
export class GetAuthorArgs {
@Field({ nullable: true })
firstName?: string;
@Field({ nullable: true })
lastName?: string;
}
提示 同样,由于TypeScript的元数据反射系统的限制,您必须使用@Field()装饰器手动指示类型和可选性,或使用CLI插件。
现在我们可以在解析器中使用以下方法签名:
@Query(returns => Author)
async author(@Args() args: GetAuthorArgs): Promise<Author> {
return this.authorsService.findOneByName(args.firstName, args.lastName);
}
类验证器集成
Nest与类验证器库很好地集成。这种强大的组合允许您对传入的GraphQL查询使用基于装饰器的验证。例如:
@@filename(dto/get-author.args)
import { IsOptional, Length } from 'class-validator';
import { ArgsType, Field } from '@nestjs/graphql';
@ArgsType()
export class GetAuthorArgs {
@Field({ nullable: true })
@IsOptional()
@Length(3, 50)
firstName?: string;
@Field({ nullable: true })
@IsOptional()
@Length(3, 50)
lastName?: string;
}
模式优先
在模式优先方法中,我们首先手动定义GraphQL模式,然后实现解析器。
假设我们为我们的GraphQL API定义了以下模式(在.graphql文件中):
type Query {
recipes: [Recipe!]!
recipe(id: ID!): Recipe
}
type Recipe {
id: ID!
title: String!
description: String
creationDate: Date!
ingredients: [String!]!
}
现在我们可以创建一个解析器类:
@@filename(recipes.resolver)
@Resolver('Recipe')
export class RecipesResolver {
constructor(private readonly recipesService: RecipesService) {}
@Query()
async recipes() {
return this.recipesService.findAll();
}
@Query('recipe')
async getRecipe(@Args('id') id: string) {
return this.recipesService.findOneById(id);
}
}
在上面的示例中,我们创建了一个RecipesResolver类,它定义了一个查询解析器函数和一个字段解析器函数。由于我们使用的是模式优先方法,解析器方法的名称是任意的。
或者,您可以使用以下语法:
@@filename(recipes.resolver)
@Resolver('Recipe')
export class RecipesResolver {
constructor(private readonly recipesService: RecipesService) {}
@Query('recipes')
async getRecipes() {
return this.recipesService.findAll();
}
@Query('recipe')
async getRecipe(@Args('id') id: string) {
return this.recipesService.findOneById(id);
}
}
如果您想要将解析器函数与模式中的名称匹配,您可以使用以下方法:
@@filename(recipes.resolver)
@Resolver('Recipe')
export class RecipesResolver {
constructor(private readonly recipesService: RecipesService) {}
@Query()
recipes() {
return this.recipesService.findAll();
}
@Query()
recipe(@Args('id') id: string) {
return this.recipesService.findOneById(id);
}
}
生成类型
假设我们使用模式优先方法并启用了TypeScript定义生成功能(如前一章所示),一旦您运行应用程序,将生成以下文件:
@@filename(graphql.ts)
export class Recipe {
id: string;
title: string;
description?: string;
creationDate: Date;
ingredients: string[];
}
export interface IQuery {
recipes(): Recipe[] | Promise<Recipe[]>;
recipe(id: string): Recipe | Promise<Recipe>;
}
现在,我们可以使用接口来键入我们的解析器类:
@@filename(recipes.resolver)
@Resolver('Recipe')
export class RecipesResolver implements IQuery {
constructor(private readonly recipesService: RecipesService) {}
recipes(): Recipe[] {
return this.recipesService.findAll();
}
recipe(id: string): Recipe {
return this.recipesService.findOneById(id);
}
}
提示 要了解更多关于生成TypeScript定义的信息,请参阅此章节。
装饰器
您可能会注意到,我们在上面的示例中引用了相同的装饰器,但没有任何类型函数(例如,@Query(returns => [Recipe])变成了@Query())。这是因为在模式优先方法中,类型定义在SDL文件中,而不是通过装饰器。
模块
一旦我们创建了解析器,我们需要在模块中注册RecipesResolver:
@@filename(recipes.module)
@Module({
imports: [RecipesService],
providers: [RecipesResolver],
})
export class RecipesModule {}
访问用户载荷
在某些情况下,您可能需要访问当前经过身份验证的用户载荷。您通常通过将令牌与请求一起发送,然后在服务器端验证令牌并提取用户载荷来实现此目的。
在GraphQL应用程序中,用户载荷通过执行上下文传递。我们可以通过添加@Context()参数装饰器来访问上下文:
@Query(returns => Recipe)
async recipe(@Args('id') id: string, @Context() ctx) {
console.log(ctx.user); // 打印经过身份验证的用户
return this.recipesService.findOneById(id);
}
提示 要了解更多关于GraphQL上下文的信息,请访问此链接。
信息对象
解析器的另一个可能有用的参数是info对象,它包含有关执行状态的信息,包括字段名称、从根到字段的路径等。您可以使用@Info()装饰器访问此对象:
@Query(returns => Recipe)
async recipe(@Args('id') id: string, @Info() info) {
console.log(info.fieldName); // 打印 "recipe"
return this.recipesService.findOneById(id);
}
工作示例
完整的工作示例可在这里获得。