Nest 最新的版本为V9,较之前的V7/8有不小改动,GraphQL 部分官方文档之前的V8也是本人翻译的,有很多不完善的地方,这次打算重新精细翻译一遍,会持续更新这块内容,并最后贡献给中文文档仓库,为中文社区贡献一份力量。有兴趣的小伙伴记得要关注收藏。
在GraphQL世界中,关于处理身份验证或操作的副作用等问题存在很多争论。我们是否应该在业务逻辑内处理这些问题吗?我们是否应该使用高阶函数来增强具有授权逻辑的查询和突变?亦或我们是否应该使用schema 指令?这些问题没有一个千篇一律的答案。
Nest通过其跨平台功能(如守卫和拦截器)帮助解决这些问题。其理念是减少冗余并提供有助于创建结构良好、可读且一致的应用程序的工具。
概览
在GraphQL中,你可以像任何RESTful应用程序一样,以同样的方式使用标准守卫、拦截器、过滤器和管道。此外,你可以通过利用[自定义装饰器](by leveraging the custom decorators feature)功能,轻松创建你自己的装饰器。让我们看一下这个GraphQL查询处理器示例。
@Query('author')
@UseGuards(AuthGuard)
async getAuthor(@Args('id', ParseIntPipe) id: number) {
return this.authorsService.findOneById(id);
}
如你所见,GraphQL与守卫和管道一起工作的方式,与HTTP REST处理器的方式相同。因此,你可以将验证逻辑移到守卫。你甚至可以跨REST和GraphQL API接口,重用同一个守卫类。同样,拦截器以相同的方式在两种类型的应用程序中工作:
@Mutation()
@UseInterceptors(EventsInterceptor)
async upvotePost(@Args('postId') postId: number) {
return this.postsService.upvoteById({ id: postId });
}
执行上下文
因为GraphQL在传入请求中接受不同类型的数据,所以守卫和拦截器所接收的执行上下文在GraphQL和REST中有所不同。GraphQL解析器有一组独特的参数:root,args,context,和info。因此守卫和拦截器必须将通用的ExecutionConttext转化为GqlExecutionContext。这很简单:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
return true;
}
}
GqlExecution.create() 返回的GraphQL上下文对象为每个GraphQL解析器参数暴露了一个get方法(例如,getArgs(),getContext() 等)。转换后,我们就可以轻松地为当前请求挑选出任意GraphQL参数了。
异常过滤器
Nest标准异常过滤器也同样兼容于GraphQL应用程序。与ExecutionContext一样,GraphQL应用也需要将ArgumentsHost对象转换为 GqlArgumentsHost对象。
@Catch(HttpException)
export class HttpExceptionFilter implements GqlExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const gqlHost = GqlArgumentsHost.create(host);
return exception;
}
}
提示
GqlExceptionFilter和GqlArgumentsHost都是从@nestjs/graphql包里导入的。
注意,与REST情况不同的是,你不需要使用本地response对象来生成响应。
自定义装饰器
如之前提到的,自定义装饰器功能也可以和GraphQL解析器一起按预期工作。
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) =>
GqlExecutionContext.create(ctx).getContext().user,
);
如下使用@User() 自定义装饰:
@Mutation()
async upvotePost(
@User() user: UserEntity,
@Args('postId') postId: number,
) {}
提示 上述示例中,我们假定了
user对象在你的GraphQL应用程序中已经被指派到了上下文中。
字段解析器级执行增强器
在GraphQL 上下文中,Nest不会在字段层级运行增强器(拦截器、守卫和过滤的通用叫法),查看这个问题:它们只针对顶层的@Query()/@Mutataion() 方法运行。你可以通过在GqlModuleOptions中设置fieldResolverEnhancers选项来告诉Nest为使用@ResolveField() 注释的方法执行拦截器、守卫或过滤器。视情况向其传递“拦截器“、“守卫“和/或“过滤器“列表:
GraphQLModule.forRoot({
fieldResolverEnhancers: ['interceptors']
}),
警告 ⚠️ 当你返回的记录很多而且你的字段解析器会被执行上千次时,启用字段级增强器会引发性能问题。由于这个原因,当你开启
fieldResolverEnhancers,我们建议你跳过那些对你的字段解析器来说不是绝对必要的增强器的执行。你可以使用以下辅助函数来执行此操作:
export function isResolvingGraphQLField(context: ExecutionContext): boolean {
if (context.getType<GqlContextType>() === 'graphql') {
const gqlContext = GqlExecutionContext.create(context);
const info = gqlContext.getInfo();
const parentType = info.parentType.name;
return parentType !== 'Query' && parentType !== 'Mutation';
}
return false;
}
创建自定义驱动程序
Nest提供了两个开箱即用的官方驱动程序:@nestjs/apollo和@nestjs/mercurius,同时也允许开发人员构建新的自定义驱动程序的API。使用自定义驱动程序,你可以集成任何GraphQL库或扩展现有的集成,在顶层添加额外的功能。
例如,要集成express-graphql包,你可以创建如下的驱动程序类:
import { AbstractGraphQLDriver, GqlModuleOptions } from '@nestjs/graphql';
import { graphqlHTTP } from 'express-graphql';
class ExpressGraphQLDriver extends AbstractGraphQLDriver {
async start(options: GqlModuleOptions<any>): Promise<void> {
options = await this.graphQlFactory.mergeWithSchema(options);
const { httpAdapter } = this.httpAdapterHost;
httpAdapter.use(
'/graphql',
graphqlHTTP({
schema: options.schema,
graphiql: true,
}),
);
}
async stop() {}
}
然后像下面这样使用它:
GraphQLModule.forRoot({
driver: ExpressGraphQLDriver,
});