NestJS 9 GraphQL 中文文档(九) - 字段中间件

235 阅读4分钟

写在前面的话

Nest 最新的版本为V9,较之前的V7/8有不小改动,GraphQL 部分官方文档之前的V8也是本人翻译的,有很多不完善的地方,这次打算重新精细翻译一遍,会持续更新这块内容,并最后贡献给中文文档仓库,为中文社区贡献一份力量。有兴趣的小伙伴记得要关注收藏。

警告⚠️
此章节仅适用于代码优先方式。

字段中间件能让你在字段被解析之前或之后运行任意代码。字段中间件可以被用来转换字段结果,验证字段参数,或者甚至检查字段级别角色(例如,需要访问执行中间件函数的目标字段)。

你可以在一个字段上连接多个中间件函数。在这种情况下,他们将沿着中间件的调用顺序进行链式调用。中间件函数在middleware数组中的顺序很重要。第一个解析器是“最外“层,所以它在最前和最后执行(类似graphql-middleware包)。第二个解析器是“次外“层,所以它在第二位和倒数第二位执行。

入门

让我们从创建一个简单的中间件开始,此中间件将在一个字段的值被返回到客户端之前记录它:

import { FieldMiddleware, MiddlewareContext, NextFn } from '@nestjs/graphql';

const loggerMiddleware: FieldMiddleware = async (
  ctx: MiddlewareContext,
  next: NextFn,
) => {
  const value = await next();
  console.log(value);
  return value;
};

提示
MiddlewareContext是一个对象,它包含的对象与通常由GraphQL解析器函数接受的参数相同({source, args, context, info}),而NextFn是一个函数,可让你执行堆栈中的下一个中间件(绑定到该字段)或实际的字段解析器。

警告⚠️
字段中间件函数不可以注入依赖或访问Nest的DI容器,因为他们被设计得非常轻量化,而且不应该执行任何潜在的耗时的操作(像从数据库查询数据)。如果你需要调用外部服务/从数据源查询数据,你应该在绑定到根查询/突变处理器上的守卫/拦截器中进行,并将其分配给你可以从字段中间件中访问的context对象(具体来说,是来自MiddlewareContext对象)。

注意,字段中间件必须匹配FieldMiddleware接口。在上面的示例中,我们先运行next() 函数(执行实际字段解析器并返回一个字段值),然后,我们把这个值记录到我们的终端。此外,从中间件返回的值完全覆盖了先前的值,并且由于我们不想执行任何更改,所以我们只返回了原始值。

有了这个,我们可以直接在@Field() 装饰器中注册我们的中间件了,如下所示:

@ObjectType()
export class Recipe {
  @Field({ middleware: [loggerMiddleware] })
  title: string;
}

现在,无论我们何时请求Recipe对象类型中的title字段,原始字段的值都将被记录到控制台。

提示
要了解如何使用扩展功能实现字段级权限系统,请查阅此章节

另外,正如上面所提到的,我们可以借助中间件函数控制字段的值。出于演示目的,让我们把recipe 的标题转成大写(如果有的话):

const value = await next();
return value?.toUpperCase();

在这种情况下,当被请求时,所有标题都将被自动转为大写。

同样的,你也可以把字段中间件绑定到自定义字段解析器(用@ResolveField() 装饰器注释的方法),如下所示:

@ResolveField(() => String, { middleware: [loggerMiddleware] })
title() {
  return 'Placeholder';
}

警告⚠️
如果在字段解析器级别启动了增强器(阅读更多),字段中间件函数将在绑定到方法的任何拦截器、守卫等之前运行(但是会在查询或突变处理器上注册的根级增强器之后)。

全局字段中间件

除了直接将中间件绑定到特定字段,你还可以注册一个或多个全局中间件函数。在这种情况下,他们将会被自动连接到对象类型的所有字段上。

GraphQLModule.forRoot({
  autoSchemaFile: 'schema.gql',
  buildSchemaOptions: {
    fieldMiddleware: [loggerMiddleware],
  },
}),

提示
全局注册的字段中间件函数将会在本地注册的(那些直接绑定到特定字段上)之前执行。