ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TypeGraphQL 中间件与守卫实战指南:从装饰器挂载到全局拦截的完整实现

TypeGraphQL 中间件与守卫实战指南:从装饰器挂载到全局拦截的完整实现 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载本文是一份围绕 TypeGraphQL 中间件Middleware体系的实战指南基于website/versioned_docs/version-2.0.0-rc.1/middlewares.md文档及仓库源码展开。你将掌握如何编写函数式与类式中间件、利用next控制执行栈、实现守卫与错误拦截并通过UseMiddleware()装饰器或buildSchema的globalMiddlewares配置将中间件挂载到解析器与字段上最终获得可直接运行的真实示例代码。中间件是什么koa 风格的可复用代码片段中间件是 TypeGraphQL 中一类可复用的代码片段可以轻松附加到解析器resolver和字段field上。通过中间件我们可以把解析器中重复出现的公共逻辑如耗时统计、访问日志、错误处理、权限校验抽离出来再用装饰器声明式地挂载甚至可以通过全局注册一次性作用于所有操作。TypeGraphQL 的中间件模型受到 koa.js 的启发。从 src/typings/middleware.ts 的类型定义可以看出一个中间件本质上是接收两个参数的函数resolver data与解析器收到的数据一致包含root、args、context、info四个字段类型为ResolverDataTContext见 src/typings/resolver-data.tsnext函数用于控制下一个中间件以及最终解析器的执行返回类型为Promiseany即NextFn。与 express.js 最大的区别在于next返回的是后续中间件与解析器执行结果的 Promise而不是简单的空 Promise。这个设计让“在解析器执行前后做事情”变得非常自然——你可以先拿到next()的 Promise再在其then/await之后读取最终结果从而轻松实现耗时统计等需求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]); };仓库示例 examples/middlewares-custom-decorators/middlewares/resolve-time.ts 中ResolveTimeMiddleware的实现与此完全一致可直接对照参考。拦截执行结果中间件不仅能“围观”解析器的执行还能拦截并替换其返回结果。借助next()返回结果 Promise 的特性我们可以把原始结果加工后重新返回export const CompetitorInterceptor: MiddlewareFn async (_, next) { const result await next(); if (result typegql) { return type-graphql; } return result; };从普通使用者的角度看这个能力似乎没什么用武之地但它最初是为插件系统与第三方库集成设计的借助结果替换可以在不修改业务代码的前提下把返回对象包装成惰性关系加载lazy relation包装器在用户按需访问属性时才自动从数据库拉取关联数据。简单中间件只做前置动作如果只需要在某个动作发生前执行逻辑例如记录访问日志可以把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(); };这种写法下中间件自身不需要是async函数返回next()即可保持执行链的连续性。守卫Guard中断执行栈中间件的强大之处在于它可以打破中间件栈——只要不调用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(); };这个守卫同时演示了三种分支直接返回结果、抛错终止、放行到下一个中间件/解析器。注意参数args在ResolverData中就是一个普通对象访问方式与解析器内一致。可复用中间件中间件工厂有些中间件需要按场景配置就像给Authorized()装饰器传roles数组一样参见 docs/authorization.md。此时应创建中间件工厂——一个接收配置参数、返回中间件的函数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——后者会把函数本身当作中间件执行导致配置丢失。仓库示例 examples/middlewares-custom-decorators/middlewares/number-interceptor.ts 提供了此中间件的完整实现。错误拦截器中间件也可以捕获执行过程中抛出的错误从而统一记录日志、过滤掉不能暴露给用户的信息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; } };注意这里的throw err分支——当错误无需隐藏时显式重新抛出是为了让错误沿 GraphQL 错误链路继续传播。仓库示例 examples/middlewares-custom-decorators/middlewares/error-logger.ts 展示了一个实战化版本它通过typedi注入Logger服务把错误消息、操作类型、字段名和当前用户记录到日志同时只对ArgumentValidationError放行原始错误其余一律替换为“Unknown error occurred. Try again later!”避免数据库 SQL 语句等敏感信息泄漏。类式中间件依赖注入与可测试性当中间件逻辑变复杂——需要访问数据库、写文件日志、与第三方服务通信时函数式中间件往往难以测试。此时可以改用类式中间件从而享受依赖注入DI带来的可测性例如轻松 mock 文件日志器或数据库仓库详见 docs/dependency-injection.md。类式中间件需要实现MiddlewareInterfaceTContext接口并在类中提供与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.ts 可以看到MiddlewareInterface只有use: MiddlewareFnTContext一个成员而MiddlewareClass是构造函数类型Middleware联合类型同时接受函数与类——这意味着UseMiddleware()和globalMiddlewares对两种形态都开箱即用。在底层执行时见 src/resolvers/helpers.ts 的applyMiddlewaresTypeGraphQL 会通过判断currentMiddleware.prototype ! undefined来区分函数与类对于类中间件它会从 IOC 容器container.getInstance获取实例并绑定use方法同时该函数实现了 koa 式的洋葱模型——dispatchHandler递归调度中间件栈并带有“next()被多次调用会抛出next() called multiple times错误”的保护机制。另外applyMiddlewares中result ! undefined ? result : nextResult的逻辑意味着如果中间件不返回任何值会自动透传下游结果这保证了类似LogAccess这种只做记录、不显式return next()的中间件也能正确工作。如何挂载中间件使用UseMiddleware()装饰器在字段或解析器声明上方放置UseMiddleware()装饰器即可挂载中间件。它接收一个中间件数组数组内顺序即执行顺序也可以不使用数组直接传多个参数——装饰器签名同时支持两种写法见 src/decorators/UseMiddleware.ts底层通过getArrayFromOverloadedRest见 src/helpers/decorators.ts统一归一为数组。挂载到解析器Resolver() export class RecipeResolver { Query() UseMiddleware(ResolveTime, LogAccess) randomValue(): number { return Math.random(); } }挂载到ObjectType字段与Authorized()的用法一致参见 docs/authorization.mdObjectType() export class Recipe { Field() title: string; Field(type [Int]) UseMiddleware(LogAccess) ratings: number[]; }装饰器内部src/decorators/UseMiddleware.ts会根据目标对象把元数据写入存储挂在类上时调用collectResolverMiddlewareMetadata挂在字段/方法上时调用collectMiddlewareMetadata存储实现见 src/metadata/metadata-storage.ts并拒绝 symbol 类型的方法名抛出SymbolKeysNotSupportedError。执行顺序从 src/resolvers/create.ts 可以确认挂载中间件的三类入口——createHandlerResolverQuery/Mutation/Subscription 等处理方法、createAdvancedFieldResolverFieldResolver与createBasicFieldResolverObjectType 字段都会执行globalMiddlewares.concat(具体元数据里的 middlewares)也就是说全局中间件总是排在局部中间件之前局部中间件内部按装饰器传入顺序执行。此外若同时使用了Authorized()applyAuthChecker见 src/resolvers/helpers.ts会把认证中间件unshift到最前保证鉴权最先执行。注册全局中间件对于测量耗时、捕获错误这类通用中间件逐个在字段/解析器上写UseMiddleware(ResolveTime)显然很繁琐。TypeGraphQL 支持注册全局中间件——它会作用于每一个 Query、Mutation、Subscription 和字段解析器。只需使用buildSchema配置对象中的globalMiddlewares属性const schema await buildSchema({ resolvers: [RecipeResolver], globalMiddlewares: [ErrorInterceptor, ResolveTime], });在 src/schema/build-context.ts 中BuildContext会把该选项透传给createHandlerResolver等工厂函数若不传则默认初始化为空数组src/schema/build-context.ts。仓库示例 examples/middlewares-custom-decorators/index.ts 展示了完整用法buildSchema中同时传入globalMiddlewares: [ErrorLoggerMiddleware]与container: Container让类式错误日志中间件可以正常从 typedi 容器解析依赖。自定义装饰器封装中间件如果希望中间件以更语义化、声明式的 API 暴露例如LogAccess()或NumberInterceptor(3.0)可以基于UseMiddleware创建自定义方法装饰器。具体做法参见 docs/custom-decorators.md 的 “Method Decorators” 一节。仓库示例 examples/middlewares-custom-decorators/decorators 提供了参考实现例如把UseMiddleware(ResolveTimeMiddleware)封装成自定义装饰器让业务代码表达意图更清晰。完整示例middlewares-custom-decorators仓库中的 examples/middlewares-custom-decorators 示例把上述各种中间件形态组合成了一个可运行的完整应用值得逐一阅读middlewares/resolve-time.ts函数式耗时统计中间件middlewares/log-access.ts函数式访问日志中间件middlewares/number-interceptor.ts中间件工厂带minValue配置middlewares/error-logger.ts基于 typedi 依赖注入的类式错误拦截器recipe/recipe.resolver.ts类上使用UseMiddleware(ResolveTimeMiddleware)方法上组合自定义装饰器ValidateArgs与CurrentUserindex.tsbuildSchema中注册全局中间件ErrorLoggerMiddleware并配合container: Container与context注入最终启动 Apollo Server。运行方式在该示例目录下安装依赖后执行入口文件TypeGraphQL 会生成schema.graphql文件并启动监听 4000 端口的 GraphQL 服务器可通过 GraphQL 客户端发起查询验证中间件的日志输出、参数校验与错误隐藏行为。小结TypeGraphQL 的中间件体系覆盖了从“前置记录”到“结果改写”“栈中断守卫”“错误拦截”“类式 DI 注入”的完整能力矩阵。核心要点可归纳为函数签名中间件接收ResolverData与返回 Promise 的nextnext的结果 Promise 使得前后置逻辑与结果改写皆可实现类型见 src/typings/middleware.ts三种挂载方式UseMiddleware挂载到类、方法或字段globalMiddlewares全局注册自定义装饰器封装执行顺序全局中间件在前、局部中间件按声明顺序在后、认证中间件Authorized永远排最前见 src/resolvers/create.ts类式中间件实现MiddlewareInterface由容器实例化天然支持依赖注入与单测 mock。掌握了这些机制就可以把横切关注点耗时、日志、鉴权、错误脱敏从业务解析器中彻底剥离让 GraphQL 层保持干净、可维护、可测试。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 中间件与守卫Middleware Guards完全指南从装饰器到全局拦截TypeGraphQL 中间件与守卫Middleware Guards完全指南从装饰器到全局拦截 导读 中间件Middleware是 TypeGr后端GraphQLAPI设计TypeGraphQL 中间件Middlewares完整实战指南从守卫到全局拦截器TypeGraphQL 中间件Middlewares完整实战指南从守卫到全局拦截器 本指南以 TypeGraphQL 官方文档中的中间件章节为主线系统讲后端GraphQLAPI设计TypeGraphQL 中间件Middleware与守卫Guard实战指南从装饰器挂载到全局注册的完整解析TypeGraphQL 中间件Middleware与守卫Guard实战指南从装饰器挂载到全局注册的完整解析 中间件是 TypeGraphQL 中用于复后端GraphQLAPI设计上一篇规则引擎终极指南5种高效配置方法与动态管理技巧下一篇Hacker Typer3分钟让你变身编程大神的终极模拟器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表