写在前面的话
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],
},
}),
提示
全局注册的字段中间件函数将会在本地注册的(那些直接绑定到特定字段上)之前执行。