
后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载TypeGraphQL 的核心魅力在于用 TypeScript 类与装饰器声明式地构建 GraphQL Schema。但当多个 Resolver 需要重复执行同一段逻辑如参数校验、字段投影计算、当前用户提取时样板代码会随之膨胀。自定义装饰器正是为此而生它能将公共逻辑封装为带语义的装饰器让代码既简洁又易于单元测试。本篇基于 TypeGraphQL 2.0.0-rc.1 官方文档website/versioned_docs/version-2.0.0-rc.1/custom-decorators.md结合仓库源码与examples/middlewares-custom-decorators实战示例系统讲解方法装饰器与参数装饰器的创建、原理与最佳实践。为什么需要自定义装饰器在 TypeGraphQL 中内置装饰器Query、Mutation、Arg、Ctx、Authorized等已经帮我们把 Resolver 声明得足够干净。但业务逻辑千变万化比如每个变更操作前都要基于 Joi/class-validator 校验参数每个查询都要从context中取出当前登录用户某些查询需要根据客户端请求的字段集合GraphQLinfo动态构建数据库 select 投影。这些逻辑如果散落在每个 Resolver 内部会产生大量重复代码如果塞进context再手工取出又会污染上下文对象。TypeGraphQL 提供了两条官方路径来封装这类公共逻辑方法装饰器Method decorators本质是中间件Middleware的语法糖可挂在 Resolver 方法或字段上参数装饰器Parameter decorators可将计算结果注入为 Resolver 方法的参数。在 2.0.0-rc.1 文档中TypeGraphQL 明确支持这两类自定义装饰器而在当前仓库的最新文档docs/custom-decorators.md中还额外引入了第三类——Resolver 类装饰器本篇也会一并介绍。方法装饰器Method Decorators与中间件的关系TypeGraphQL 的中间件机制允许我们把可复用逻辑写成MiddlewareFn形式的函数再通过UseMiddleware附加到 Resolver 上。自定义方法装饰器做的事情本质上就是把创建中间件和挂载中间件两步合并成一步——它返回的正是UseMiddleware装饰器的调用结果。创建方法装饰器createMethodMiddlewareDecorator以基于 Joi schema 校验参数为例官方文档给出的工厂函数如下export function ValidateArgs(schema: JoiSchema) { return createMethodDecorator(async ({ args }, next) { // Middleware code that uses custom decorator arguments // e.g. Validation logic based on schema using joi await joiValidate(schema, args); return next(); }); }需要特别说明一点 API 命名的演进2.0.0-rc.1 文档中使用的辅助函数名为createMethodDecorator而在当前仓库源码src/decorators/createMethodMiddlewareDecorator.ts与最新文档中该函数已更名为createMethodMiddlewareDecorator二者功能完全等价。撰写代码时请以当前版本导出的名为准从type-graphql包中导入import { createMethodMiddlewareDecorator } from type-graphql; export function ValidateArgs(schema: JoiSchema) { return createMethodMiddlewareDecorator(async ({ args }, next) { await joiValidate(schema, args); return next(); }); }查看源码可见其实现非常直白——它就是一个对UseMiddleware的类型化包装export function createMethodMiddlewareDecoratorTContextType extends object object( resolver: MiddlewareFnTContextType, ): MethodDecorator { return UseMiddleware(resolver); }也就是说自定义方法装饰器返回的本质上就是一个UseMiddleware装饰器中间件逻辑的完整能力前置处理、调用next()继续执行、拦截/替换返回值、抛出异常中断在这里全部可用。中间件签名定义于 src/typings/middleware.tsexport type NextFn () Promiseany; export type MiddlewareFnTContext extends object object ( action: ResolverDataTContext, next: NextFn, ) Promiseany;而ResolverData见 src/typings/resolver-data.ts与 Resolver 收到的参数一致包含root、args、context、info四个字段这意味着自定义装饰器内可以访问到 Resolver 的全部执行上下文。使用自定义方法装饰器创建完成后用法与内置装饰器无异——把它放在 Resolver 方法上方并传入配置参数即可还可以与显式的UseMiddleware混用执行顺序按装饰器从上到下的声明排列Resolver() export class RecipeResolver { ValidateArgs(MyArgsSchema) // Custom decorator UseMiddleware(ResolveTime) // Explicit middleware Query() randomValue(Args() { scale }: MyArgs): number { return Math.random() * scale; } }底层元数据收集机制从 src/decorators/UseMiddleware.ts 的实现可以看到UseMiddleware支持两种调用形式数组或可变参数并会根据是否传入propertyKey区分挂载目标挂载在类上propertyKey null调用collectResolverMiddlewareMetadata中间件对该类的所有 Resolver 生效挂载在方法/属性上调用collectMiddlewareMetadata仅对该字段生效。此外若propertyKey是symbol会抛出SymbolKeysNotSupportedErrorsrc/errors/SymbolKeysNotSupportedError.ts因此自定义装饰器不支持 symbol 键名的方法。Resolver 类装饰器Resolver Class Decorators在最新文档docs/custom-decorators.md中TypeGraphQL 还提供了与createMethodMiddlewareDecorator对称的类级辅助函数createResolverClassMiddlewareDecorator其源码src/decorators/createResolverClassMiddlewareDecorator.ts同样只是UseMiddleware(resolver)的包装返回ClassDecoratorexport function ValidateArgs(schema: JoiSchema) { return createResolverClassMiddlewareDecorator(async ({ args }, next) { await joiValidate(schema, args); return next(); }); }用法上只需要把装饰器放到 Resolver 类上该类的所有 Query/Mutation 都会自动应用这段逻辑无需逐方法重复标注ValidateArgs(MyArgsSchema) // Custom decorator UseMiddleware(ResolveTime) // Explicit middleware Resolver() export class RecipeResolver { Query() randomValue(Args() { scale }: MyArgs): number { return Math.random() * scale; } }参数装饰器Parameter Decorators核心思想把返回值注入为方法参数参数装饰器与中间件/方法装饰器的最大区别在于它可以返回一个值该值会被注入到 Resolver 方法的对应参数中。这大大减少了通过context在中间件与 Resolver 之间传值的污染性写法——过去我们不得不在中间件里往context塞数据、在 Resolver 里再取出来现在直接由装饰器产出参数即可。参数装饰器可以只是一个简单的数据提取器例如从context中取出当前用户这让 Resolver 变得对单元测试更友好测试时直接传入 mock 的context即可function CurrentUser() { return createParamDecoratorMyContextType(({ context }) context.currentUser); }同样地该辅助函数在当前仓库源码中的正式名称是createParameterDecoratorsrc/decorators/createParameterDecorator.ts2.0.0-rc.1 文档中的createParamDecorator是它的旧名。源码中它的类型签名为export type ParameterResolverTContextType extends object object ( resolverData: ResolverDataTContextType, ) any; export function createParameterDecoratorTContextType extends object object( resolver: ParameterResolverTContextType, paramOptions: CustomParameterOptions {}, ): ParameterDecorator可以看到参数解析函数接收完整的ResolverDataroot、args、context、info因此理论上你可以基于任意上下文数据计算注入值。进阶用法基于 GraphQL info 计算字段映射参数装饰器还可以封装更复杂的逻辑。与中间件相比它提供了更细粒度的按需执行控制——例如仅在 Resolver 明确声明Fields()参数时才计算字段映射而不是在每个请求里都无条件执行function Fields(level 1): ParameterDecorator { return createParameterDecorator(async ({ info }) { const fieldsMap: FieldsMap {}; // Calculate an object with info about requested fields // based on GraphQL info parameter of the resolver and the level parameter // or even call some async service, as it can be a regular async function and we can just await return fieldsMap; }); }注意把参数装饰器的逻辑写成async函数会拖慢 GraphQL Resolver 的执行每次调用都会产生额外的 Promise 开销所以如无必要尽量保持参数解析函数为同步逻辑。在 Resolver 中使用参数装饰器自定义参数装饰器的使用方式与内置装饰器Args、Arg、Ctx完全一致直接放在参数声明前即可Resolver() export class RecipeResolver { constructor(private readonly recipesRepository: RepositoryRecipe) {} Authorized() Mutation(returns Recipe) async addRecipe( Args() recipeData: AddRecipeInput, // Custom decorator just like the built-in one CurrentUser() currentUser: User, ) { const recipe: Recipe { ...recipeData, // and use the data returned from custom decorator in the resolver code author: currentUser, }; await this.recipesRepository.save(recipe); return recipe; } Query(returns Recipe, { nullable: true }) async recipe( Arg(id) id: string, // Custom decorator that parses the fields from GraphQL query info Fields() fields: FieldsMap, ) { return await this.recipesRepository.find(id, { // use the fields map as a select projection to optimize db queries select: fields, }); } }在addRecipe中CurrentUser()注入的currentUser直接参与业务组装在recipe中Fields()注入的字段映射被用作数据库查询的 select 投影从而按需优化查询性能。运行时注入原理参数装饰器的执行发生在 Resolver 调用之前。查看 src/resolvers/helpers.ts 中的getParamValues逻辑其中kind custom的分支会若该自定义参数携带了arg元数据见下文自定义 Arg 装饰器先对参数值执行convertArgToInstance与校验调用paramInfo.resolver(resolverData)得到注入值最终通过Promise.all(paramValues)等待所有 Promise 形式的参数值再调用 Resolver 方法。对应的元数据结构定义在 src/metadata/definitions/param-metadata.tsCustomParamMetadata记录了kind: custom、目标类、方法名、参数索引以及可选的options.arg参数注册信息。进阶自定义 Arg 装饰器Custom Arg Decorators有时我们希望自定义装饰器不仅能解析值还能同时在 GraphQL Schema 中注册/暴露一个参数。在 2.0.0-rc.1 之后TypeGraphQL 为createParameterDecorator增加了第二个参数CustomParameterOptions其中arg键可携带Arg所需的全部元数据名称、类型函数、选项从而避免同时调用Arg()与createParameterDecorator()导致内部元数据冲突的问题function RandomIdArg(argName id) { return createParameterDecorator( // here we do the logic of getting provided argument or generating a random one ({ args }) args[argName] ?? Math.round(Math.random() * MAX_ID_VALUE), { // here we provide the metadata to register the parameter as a GraphQL argument arg: { name: argName, typeFunc: () Int, options: { nullable: true, description: Accepts provided id or generates a random one., }, }, }, ); }结合源码可以看到当传入paramOptions.arg时createParameterDecorator.ts 会通过getParamInfosrc/helpers/params.ts从design:paramtypes元数据或typeFunc推断 GraphQL 类型并收集kind: arg的参数元数据当运行时存在options.arg时参数值还会先经过与内置Arg相同的校验流程见 src/resolvers/helpers.ts。使用方式与普通Arg几乎一致Schema 中会自动出现id参数Resolver() export class RecipeResolver { constructor(private readonly recipesRepository: RepositoryRecipe) {} Query(returns Recipe, { nullable: true }) async recipe( // custom decorator that will expose an arg in the schema RandomIdArg(id) id: number, ) { return await this.recipesRepository.findById(id); } }仓库实战示例middlewares-custom-decorators官方为自定义装饰器提供了开箱即用的完整示例位于 examples/middlewares-custom-decorators集中演示了三种自定义装饰器的落地方式。基于 class-validator 的参数校验装饰器examples/middlewares-custom-decorators/decorators/validate-args.ts 用createMethodMiddlewareDecorator封装了 class-validator 校验注释明确说明也可替换为 Joi 等其他校验库import { validate } from class-validator; import { ArgumentValidationError, type ClassType, createMethodMiddlewareDecorator, } from type-graphql; // Sample implementation of custom validation decorator // This example use class-validator however you can plug-in joi or any other validation library export function ValidateArgsT extends object(Type: ClassTypeT) { return createMethodMiddlewareDecorator(async ({ args }, next) { const instance Object.assign(new Type(), args); const validationErrors await validate(instance); if (validationErrors.length 0) { throw new ArgumentValidationError(validationErrors); } return next(); }); }在 examples/middlewares-custom-decorators/recipe/recipe.resolver.ts 中它被挂载到recipes查询上同时通过Args({ validate: false })关闭内置校验把校验职责完全交给自定义装饰器避免重复执行Query(_returns [Recipe]) ValidateArgs(RecipesArgs) async recipes( Args({ validate: false }) // Disable built-in validation here options: RecipesArgs, CurrentUser() currentUser: User, ): PromiseRecipe[] { console.log(User ${currentUser.name} queried for recipes!); const start options.skip; const end options.skip options.take; return this.items.slice(start, end); }当前用户提取装饰器examples/middlewares-custom-decorators/decorators/current-user.ts 展示了最轻量的参数装饰器——直接从context提取数据import { createParameterDecorator } from type-graphql; import { type Context } from ../context.type; export function CurrentUser() { return createParameterDecoratorContext(({ context }) context.currentUser); }随机 ID 参数装饰器examples/middlewares-custom-decorators/decorators/random-id-arg.ts 是自定义 Arg的完整实现它在参数元数据里还附加了validateFn保证传入的值落在合法区间0 到MAX_ID_VALUE之间非法输入直接抛错import { Int, createParameterDecorator } from type-graphql; const MAX_ID_VALUE 3; // Number.MAX_SAFE_INTEGER export function RandomIdArg(argName id) { return createParameterDecorator( ({ args }) args[argName] ?? Math.round(Math.random() * MAX_ID_VALUE), { arg: { name: argName, typeFunc: () Int, options: { nullable: true, description: Accepts provided id or generates a random one., validateFn: (value: number): void { if (value 0 || value MAX_ID_VALUE) { throw new Error(Invalid value for ${argName}); } }, }, }, }, ); }在同一示例的 recipe.resolver.ts 中RandomIdArg(id)被用于recipe查询未传id时自动生成随机值使 GraphQL Playground 的调试体验更友好Query(_returns Recipe, { nullable: true }) async recipe(RandomIdArg(id) id: number) { console.log(Queried for recipe with id: ${id}); return this.items.find(item item.id id); }最佳实践与注意事项优先用同步逻辑参数装饰器中的async会引入额外 Promise 开销、拖慢 Resolver能同步完成的数据提取如从context取值就不要await。用参数装饰器替代 context 传值跨中间件与 Resolver 的通信尽量走参数注入避免把业务数据塞进context造成污染与隐式依赖。命名即语义自定义装饰器的价值在于 API 表达力像ValidateArgs、CurrentUser、Fields这样的命名能让 Resolver 的意图一目了然装饰器工厂的参数如校验 schema、字段层级level使其可配置、可复用。注意 API 命名差异2.0.0-rc.1 文档中的createMethodDecorator/createParamDecorator在当前版本中对应createMethodMiddlewareDecorator/createParameterDecorator编写代码时以当前type-graphql包实际导出的 API 为准可查看 src/decorators/index.ts 确认导出清单。单测友好将公共逻辑从 Resolver 方法体剥离到装饰器后Resolver 方法变成了纯逻辑接收方测试时直接构造参数即可无需依赖中间件执行环境。小结自定义装饰器是 TypeGraphQL 生态中减少样板代码的关键机制方法装饰器createMethodMiddlewareDecorator与类装饰器createResolverClassMiddlewareDecorator本质是中间件的声明式封装负责在执行前后做点什么参数装饰器createParameterDecorator则负责向方法注入什么配合其arg元数据还能顺带在 Schema 中注册参数。掌握这三类辅助函数再结合 examples/middlewares-custom-decorators 中的现成范例你就可以把校验、鉴权、用户提取、字段投影等横切逻辑沉淀为团队内部的高质量装饰器库让每个 Resolver 都保持整洁、可读、可测试。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL 自定义装饰器完全指南用 createMethodMiddlewareDecorator 与 createParameterDecorator 消除 Resolver 样板代码TypeGraphQL 自定义装饰器完全指南用 createMethodMiddlewareDecorator 与 createParameterDecora后端GraphQLAPI设计Envoy 集成 QUICHE 深度解析会话架构、数据流水线与流控水印机制Envoy 集成 QUICHE 深度解析会话架构、数据流水线与流控水印机制 导读 本文基于 source/docs/quiche_integration.md后端GraphQLAPI设计LangChain4j Apache POI 文档解析器在 Java RAG 流水线中解析 doc、docx、ppt、xls 等 Microsoft Office 文件LangChain4j Apache POI 文档解析器在 Java RAG 流水线中解析 doc、docx、ppt、xls 等 Microsoft Offi后端GraphQLAPI设计上一篇Flux 微调模型加载失败CLIP 文本编码器缺失的完整修复指南下一篇QQ音乐加密歌曲一招解锁QMCDecode免费解密工具macOS实战上手指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考