新闻详情

新闻详情

首页 / 资讯中心 / 详情

TypeGraphQL 中间件与守卫完全指南:用 MiddlewareFn 与 @UseMiddleware 构建可复用逻辑

发布时间:2026/9/28 20:11:17来源:尧图网络
TypeGraphQL 中间件与守卫完全指南:用 MiddlewareFn 与 @UseMiddleware 构建可复用逻辑
后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载导读本文是 TypeGraphQL 官方文档中《Middleware and guards》一文的深度展开版。中间件Middleware是 TypeGraphQL 中用于在解析器resolver与字段field执行前后插入可复用代码的核心机制适合日志记录、耗时统计、结果拦截、权限守卫、错误过滤等横切关注点。读完本文你将掌握中间件的三种形态函数式、类式、工厂式、UseMiddleware装饰器与globalMiddlewares全局注册的完整用法并理解其背后的洋葱模型执行原理与源码级实现细节。什么是中间件签名、数据与 next 函数中间件是 TypeGraphQL 中最强大但也略复杂的特性。本质上中间件是一个接收两个参数的函数resolver data—— 与解析器接收的数据完全一致即root根值、args参数、context上下文、infoGraphQL 解析信息next函数—— 用于控制下一个中间件以及最终解析器的执行。你可能熟悉 express.js 的中间件模型但 TypeGraphQL 的中间件灵感源自 koa.js区别在于 TypeGraphQL 的next函数返回一个 Promise其 resolve 值为中间件栈中后续中间件与解析器的执行结果。这一点让在解析器执行前后做动作变得极其自然例如测量执行时间export const ResolveTime: MiddlewareFn async ({ info }, next) { const start Date.now(); await next(); const resolveTime Date.now() - start; console.log(${info.parentType.name}.${info.fieldName} [${resolveTime} ms]); };从源码看这些类型定义位于 src/typings/middleware.tsexport type NextFn () Promiseany; export type MiddlewareFnTContext extends object object ( action: ResolverDataTContext, next: NextFn, ) Promiseany;其中ResolverDataTContext定义为{ root, args, context, info }见 src/typings/resolver-data.ts。注意MiddlewareFn返回Promiseany这意味着中间件既可以透传结果也可以替换结果还可以抛错终止执行——这正是接下来几类中间件的基础。创建中间件拦截执行结果Interceptor中间件不仅能写日志还能拦截并替换解析器的返回值export const CompetitorInterceptor: MiddlewareFn async (_, next) { const result await next(); if (result typegql) { return type-graphql; } return result; };对本库的普通用户而言这个能力看起来用处不大但它主要是为插件系统和第三方库集成而设计的例如可以把返回对象包装进一个 lazy-relation 包装器在后台按需自动从数据库抓取关联关系。从源码角度看这一行为由applyMiddlewares中的result ! undefined ? result : nextResult逻辑支撑见 src/resolvers/helpers.ts中间件返回非undefined的值时该值会直接向上游传递并成为最终解析结果返回undefined时则自动使用next()的返回值作为结果。对应测试位于 tests/functional/middlewares.tsshould correctly intercept returned value。简单中间件只在执行前做动作如果只想在动作之前做点事例如记录访问日志把return next()放在中间件末尾即可const LogAccess: MiddlewareFnTContext ({ context, info }, next) { const username: string context.username || guest; console.log(Logging access: ${username} - ${info.parentType.name}.${info.fieldName}); return next(); };这里TContext是自定义 context 类型可让context具备类型提示。守卫Guards阻断执行栈中间件还可以通过不调用next函数来打破中间件栈此时中间件自身的返回值会直接作为结果返回解析器不再执行。也可以在需要终止执行并向用户返回错误时直接抛错例如校验参数不合规export const CompetitorDetector: MiddlewareFn async ({ args }, next) { if (args.frameworkName type-graphql) { return TypeGraphQL; } if (args.frameworkName typegql) { throw new Error(Competitive framework detected!); } return next(); };这样我们就创建了一个守卫它能在必要时阻断对解析器的访问防止执行或返回任何数据。测试 tests/functional/middlewares.ts 验证了不调用next时解析器不会被调用、错误向上传播的行为。顺带一提TypeGraphQL 内置的授权机制本质上就是一个中间件Authorized()会在 schema 构建时通过applyAuthChecker把 AuthMiddleware 插入中间件栈头部当authChecker判定无权访问时按authMode配置返回null或抛出AuthenticationError/AuthorizationError详见 docs/authorization.md。可复用中间件工厂函数有些中间件需要可配置就像向Authorized()装饰器传roles数组一样。此时应创建中间件工厂——一个接收配置参数、返回中间件的函数export function NumberInterceptor(minValue: number): MiddlewareFn { return async (_, next) { const result await next(); // hide values below minValue if (typeof result number result minValue) { return null; } return result; }; }注意挂载时必须带参数调用例如NumberInterceptor(3.0)而不是直接传NumberInterceptor本身。错误拦截器Error Interceptor中间件也能捕获执行过程中抛出的错误既可以记录日志也可以过滤掉不能返回给用户的信息export const ErrorInterceptor: MiddlewareFnany async ({ context, info }, next) { try { return await next(); } catch (err) { // write error to file log fileLog.write(err, context, info); // hide errors from db like printing sql query if (someCondition(err)) { throw new Error(Unknown error occurred!); } // rethrow the error throw err; } };该能力由applyMiddlewares的dispatchHandler在await handlerFn(...)处自然实现——next()内部 reject 的 Promise 会被外层try/catch捕获。对应测试见 tests/functional/middlewares.tserrorCatchMiddleware捕获了解析器抛出的错误并返回自定义值而middlewareErrorCatchQuery(throwError: false)则验证无错误时结果原样透传。类基础中间件支持依赖注入与测试当中间件逻辑变复杂访问数据库、写文件日志等时你可能想对它做单元测试。此时可以创建类中间件从而受益于 依赖注入轻松 mock 文件日志器或数据库仓库。实现方式实现MiddlewareInterface接口类中必须提供签名与MiddlewareFn一致的use方法。下面是前面LogAccess改造为类中间件的版本export class LogAccess implements MiddlewareInterfaceTContext { constructor(private readonly logger: Logger) {} async use({ context, info }: ResolverDataTContext, next: NextFn) { const username: string context.username || guest; this.logger.log(Logging access: ${username} - ${info.parentType.name}.${info.fieldName}); return next(); } }对应的类型定义同样在 src/typings/middleware.tsexport interface MiddlewareInterfaceTContext extends object object { use: MiddlewareFnTContext; } export type MiddlewareClassTContext extends object object new ( ...args: any[] ) MiddlewareInterfaceTContext; export type MiddlewareTContext extends object object | MiddlewareFnTContext | MiddlewareClassTContext;也就是说Middleware联合类型同时接受函数中间件与类中间件UseMiddleware()两者皆可接收。在applyMiddlewares中类中间件会通过container.getInstance()实例化支持 IOC 容器然后调用middlewareClassInstance.use.bind(middlewareClassInstance)作为处理器执行见 src/resolvers/helpers.ts。测试should correctly call class middlewaretests/functional/middlewares.ts验证了类中间件在解析器前后的完整调用链。如何挂载中间件使用 UseMiddleware 装饰器在字段或解析器声明上方放置UseMiddleware()装饰器即可挂载中间件。它接受一个中间件数组按传入顺序调用同时也支持 rest 参数形式直接传多个中间件无需数组。从源码看src/decorators/UseMiddleware.ts 通过getArrayFromOverloadedRest统一处理数组与多参数两种重载export function UseMiddleware(middlewares: ArrayMiddlewareany): MethodPropClassDecorator; export function UseMiddleware(...middlewares: ArrayMiddlewareany): MethodPropClassDecorator;用法示例Resolver() export class RecipeResolver { Query() UseMiddleware(ResolveTime, LogAccess) randomValue(): number { return Math.random(); } }也可以把中间件挂到ObjectType的字段上方式与Authorized()装饰器相同ObjectType() export class Recipe { Field() title: string; Field(type [Int]) UseMiddleware(LogAccess) ratings: number[]; }此外UseMiddleware还支持挂在解析器类上类装饰器形态propertyKey为undefined时走collectResolverMiddlewareMetadata分支见 src/decorators/UseMiddleware.ts该中间件会对类内所有字段生效。测试should call resolver middlewares in ordertests/functional/middlewares.ts验证了类级中间件 → 方法级中间件 → 解析器的执行顺序类级中间件包在最外层最先 before、最后 after。全局中间件globalMiddlewares对于耗时统计、错误捕获这类通用中间件如果每个字段/解析器都要手写UseMiddleware(ResolveTime)会非常繁琐。TypeGraphQL 因此支持注册全局中间件——它对每个 query、mutation、subscription 和字段解析器都会生效。通过buildSchema配置对象的globalMiddlewares属性注册const schema await buildSchema({ resolvers: [RecipeResolver], globalMiddlewares: [ErrorInterceptor, ResolveTime], });从源码看globalMiddlewares是 BuildContextOptions 的配置项之一构建时存入BuildContext.globalMiddlewares随后在生成解析器时src/resolvers/create.ts 分别对三种解析器形态执行globalMiddlewares.concat(resolverMetadata.middlewares!)合并createHandlerResolverquery/mutation/subscription 处理器L15-L73createAdvancedFieldResolver字段解析器L75-L118createBasicFieldResolver普通字段L120-L131。因此执行顺序恒为全局中间件 → 类级中间件 → 方法级中间件 → 解析器本体。测试should correctly call middlewares in the order of global, resolver, fieldtests/functional/middlewares.ts的日志序列完整印证了这一点globalMiddleware1 before → globalMiddleware2 before → middleware1 before → ... → resolver → ... → middleware1 after → globalMiddleware2 after → globalMiddleware1 after。自定义装饰器更声明式的 API如果想用更有描述性、更声明式的 API 使用中间件可以创建自定义方法装饰器。具体做法与可复用中间件类似区别在于需要用createMethodMiddlewareDecorator辅助函数包装中间件逻辑并返回结果详见 docs/custom-decorators.md#method-decorators。从源码看createMethodMiddlewareDecorator只是UseMiddleware(resolver)的薄封装src/decorators/createMethodMiddlewareDecorator.ts而createResolverClassMiddlewareDecorator则对应类装饰器形态src/decorators/createResolverClassMiddlewareDecorator.ts。示例export function ValidateArgs(schema: JoiSchema) { return createMethodMiddlewareDecorator(async ({ args }, next) { // 基于 joi schema 的校验逻辑 await joiValidate(schema, args); return next(); }); } Resolver() export class RecipeResolver { ValidateArgs(MyArgsSchema) // 自定义装饰器 UseMiddleware(ResolveTime) // 显式中间件 Query() randomValue(Args() { scale }: MyArgs): number { return Math.random() * scale; } }源码级执行原理洋葱模型与细节约束中间件的真正执行发生在applyMiddlewaressrc/resolvers/helpers.ts。其核心是一个递归的dispatchHandler(currentIndex)当currentIndex middlewares.length时执行的就是解析器本体resolverHandlerFunction否则取出middlewares[currentIndex]函数中间件直接调用类中间件经容器实例化后调用其use方法每个中间件都会拿到一个next回调内部递归调用dispatchHandler(currentIndex 1)中间件的返回值如果非undefined就向上传递否则沿用next()的返回值。由此可总结出几个关键行为约束洋葱模型整条中间件栈按全局 → 类 → 方法 → 解析器的顺序正向进入、逆向退出与 koa 一致。禁止重复调用 nextdispatchHandler内部用middlewaresIndex记录当前索引若currentIndex middlewaresIndex会抛出next() called multiple times错误。测试should throw error if middleware called next more than oncetests/functional/middlewares.ts专门验证了这一约束。返回值语义中间件返回undefined时结果自动取next()的返回值这让只做记录、不做替换的中间件写起来很省心测试见 tests/functional/middlewares.ts。类中间件实例化类中间件依赖 IOC 容器实例化因此在自定义容器场景如 typedi下可直接注入服务与 docs/dependency-injection.md 所述机制一致。完整示例middlewares-custom-decorators仓库中的 examples/middlewares-custom-decorators 示例整合展示了上述各种中间件形态middlewares/resolve-time.ts —— 函数式耗时统计中间件middlewares/log-access.ts —— 基于 typediService()的类式访问日志中间件构造函数注入Loggermiddlewares/error-logger.ts —— 类式错误拦截中间件记录错误后对非ArgumentValidationError一律抛出通用错误信息避免向用户泄露数据库 SQL 等敏感细节middlewares/number-interceptor.ts —— 工厂式可复用中间件隐藏低于阈值的数值decorators/ —— 自定义参数装饰器CurrentUser、RandomIdArg与自定义方法装饰器ValidateArgsrecipe/recipe.resolver.ts —— 在类级UseMiddleware(ResolveTimeMiddleware)与方法级ValidateArgs(RecipesArgs)同时挂载中间件并配合RandomIdArg(id)、CurrentUser()使用index.ts —— 启动入口通过globalMiddlewares: [ErrorLoggerMiddleware]注册全局错误日志中间件并注册 typedi 容器container: Container随后用 Apollo Server 提供服务。运行该示例即可直观观察每次查询都会先打印Recipe.recipe [...] ms耗时日志与访问日志出错时错误信息会被统一脱敏。小结TypeGraphQL 的中间件体系以MiddlewareFn与NextFn为基石提供了四层挂载能力全局、解析器类、方法/字段、自定义装饰器和三种编写形态函数式、类式、工厂式。它既能做横切关注点日志、耗时、鉴权也能拦截结果、捕获错误、阻断执行守卫是构建大型可维护 GraphQL 服务的关键基础设施。继续深入可阅读 docs/middlewares.md、docs/custom-decorators.md 与 docs/dependency-injection.md。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 中间件与守卫实战指南从可复用代码抽取到全局注册TypeGraphQL 中间件与守卫实战指南从可复用代码抽取到全局注册 TypeGraphQL 提供了一套 Koa 风格的中间件Middleware体系后端GraphQLAPI设计TypeGraphQL 中间件与守卫Middleware Guards完全指南从装饰器到全局拦截TypeGraphQL 中间件与守卫Middleware Guards完全指南从装饰器到全局拦截 导读 中间件Middleware是 TypeGr后端GraphQLAPI设计TypeGraphQL 中间件Middlewares完整实战指南从守卫到全局拦截器TypeGraphQL 中间件Middlewares完整实战指南从守卫到全局拦截器 本指南以 TypeGraphQL 官方文档中的中间件章节为主线系统讲后端GraphQLAPI设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

迪文T5L平台C51开发实战:双核架构、DGUS变量地址与工程化避坑指南 2026/9/28 21:28:00

迪文T5L平台C51开发实战:双核架构、DGUS变量地址与工程化避坑指南

1. 迪文T5L平台选型与整体架构拆解1.1 为什么是T5L加C51这套组合第一次接触迪文T5L平台的开发者,最常问的一个问题就是:都什么年代了,为什么还要用C51?我刚开始也有这个疑惑,毕竟现在随便一颗Cortex-M0都比传统8051内核…

阅读更多 →
别让探棒拖后腿:示波器探棒选型、校准与替代方案全解析 2026/9/28 21:27:59

别让探棒拖后腿:示波器探棒选型、校准与替代方案全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
迪文T5L平台C51与DGUS实战:从零搭建工程到ICL素材处理 2026/9/28 21:27:59

迪文T5L平台C51与DGUS实战:从零搭建工程到ICL素材处理

1. 为什么T5L平台值得单独写一篇实战指南迪文的T5L芯片在工业串口屏圈子里算是一个分水岭式的产品。早些年做串口屏项目,要么用指令集屏,发一堆十六进制指令去画控件,改个界面就得重新算坐标;要么用组态软件生成配置,灵…

阅读更多 →
RISC-V在AI算力爆发下的破局逻辑与进阶路径 2026/9/28 21:27:52

RISC-V在AI算力爆发下的破局逻辑与进阶路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
基于MSPM0G3507的循迹小车实战:PID调参与电赛H题避坑指南 2026/9/28 21:27:39

基于MSPM0G3507的循迹小车实战:PID调参与电赛H题避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
孪生神经网络与VGG16结合的点选验证码识别实践 2026/9/28 21:27:33

孪生神经网络与VGG16结合的点选验证码识别实践

简介:面向Python与深度学习初、中级学习者的孪生神经网络点选识别项目,以图片对相似度判断为核心实现点选验证码破解思路,适合用作毕设、课程设计或工程实训基线。压缩包共13个文件,约67.23MB,包含Python训练/预测脚本…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉