NestJS Logo
NestJS 中文文档
v10.0.0
  • 介绍
  • 快速上手
  • 控制器
  • 提供者
  • 模块
  • 中间件
  • 异常过滤器
  • 管道
  • 守卫
  • 拦截器
  • 自定义装饰器
  • 自定义提供者
  • 异步提供者
  • 动态模块
  • 依赖注入作用域
  • 循环依赖
  • 模块引用
  • 懒加载模块
  • 执行上下文
  • 生命周期事件
  • 发现服务
  • 跨平台无关性
  • 测试
迁移指南
API 参考
官方课程
  1. 文档
  2. GraphQL 支持
  3. 其他功能

共享模型
联邦

其他功能

在 GraphQL 世界中,关于如何处理诸如身份验证或操作的副作用等问题,存在许多讨论。我们应该在业务逻辑中处理这些问题吗?是否应该使用高阶函数为查询和变更操作增强授权逻辑?还是应该使用 schema 指令?对于这些问题,并没有一种放之四海而皆准的答案。

Nest 通过其跨平台特性(如守卫和拦截器)帮助开发者应对这些挑战。其理念是减少冗余,并提供有助于创建结构良好、可读性强且一致性高的应用程序的工具。

概述

你可以像在 RESTful 应用中一样,在 GraphQL 中使用标准的守卫、拦截器、异常过滤器和管道。此外,你还可以通过自定义装饰器功能,轻松创建自己的装饰器。下面让我们看一个 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 })
}

执行上下文(Execution Context)

由于 GraphQL 在接收请求时的数据类型与 REST 不同,守卫和拦截器所接收的执行上下文在 GraphQL 和 REST 场景下也有所区别。GraphQL 解析器拥有一组独特的参数:root、args、context 和 info。因此,守卫和拦截器需要将通用的 ExecutionContext 转换为 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
  }
}

通过 GqlExecutionContext.create() 返回的 GraphQL 上下文对象为每个 GraphQL 解析器参数都提供了 get 方法(如 getArgs()、getContext() 等)。一旦完成转换,我们就可以轻松获取当前请求的任意 GraphQL 参数。

异常过滤器(Exception filters)

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 场景不同,这里不需要使用原生的 响应对象 来生成响应。

自定义装饰器(Custom decorators)

如前所述,自定义装饰器功能在 GraphQL 解析器(resolver)中同样可以正常使用。

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 并不会在字段级别运行增强器(enhancer,增强器是拦截器、守卫和过滤器的统称),它们只会在顶层的 @Query()/@Mutation() 方法上运行。详见此 issue。你可以通过在 GqlModuleOptions 中设置 fieldResolverEnhancers 选项,让 Nest 在带有 @ResolveField() 注解的方法上执行拦截器、守卫或过滤器。只需传入包含 'interceptors'、'guards' 和/或 'filters' 的数组即可:

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 允许开发者构建新的自定义驱动(Custom Driver)。通过自定义驱动,你可以集成任意 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,
})