ARTICLE DETAIL

资讯详情

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

TypeGraphQL 参数与输入校验完整指南:class-validator 集成与自定义 validateFn 实战

TypeGraphQL 参数与输入校验完整指南:class-validator 集成与自定义 validateFn 实战 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载在 GraphQL API 开发中参数与输入数据校验是保证服务端数据质量的关键一环。本文以 TypeGraphQL2.0.0-rc.1 及后续版本内置的自动校验能力为核心系统讲解如何通过class-validator装饰器声明输入约束、如何通过buildSchema的validate与validateFn配置开关自动校验、校验失败时客户端将收到什么样的错误响应以及如何接入 Joi 等自定义校验方案。读完本文你将掌握一套装饰器声明 框架自动执行的输入校验实战方案并能结合仓库源码理解其底层实现原理。为什么需要内置参数校验TypeGraphQL 中保证输入与参数正确性的标准做法之一是使用自定义标量custom scalars例如借助graphql-custom-types提供的GraphQLEmail来校验email字段是否真的是合法邮箱地址。然而为每种数据类型信用卡号、base64、IP、URL 等逐一创建标量显然过于繁琐。为此TypeGraphQL 提供了开箱即用的参数与输入校验argument and input validation能力默认集成class-validator库借助装饰器即可轻松声明对传入数据的约束例如数字必须在 0-255 范围内、密码长度必须大于 8 个字符等。同时你也可以接入其他校验库或自研方案详见下文 自定义校验器 一节。使用 class-validator 进行自动校验安装依赖首先安装class-validator包npm install class-validator注TypeGraphQL 对class-validator采用动态导入方式按需加载详见 validate-arg.ts因此它并不是强制性的硬性依赖只有在开启校验功能时才需要真正安装。但即使关闭了校验功能为了让tsc编译不报错仍需将其作为 devDependency 保留具体见下文 注意事项。为输入类添加校验装饰器先看一个最普通的InputType()类InputType() export class RecipeInput { Field() title: string; Field({ nullable: true }) description?: string; }为它加上class-validator的装饰器后import { MaxLength, Length } from class-validator; InputType() export class RecipeInput { Field() MaxLength(30) title: string; Field({ nullable: true }) Length(30, 255) description?: string; }这样title最大长度为 30description长度被限定在 30 到 255 之间。仓库中的 automatic-validation 示例 正是采用了这一写法并且装饰器与Field()可任意排序叠加。在 buildSchema 中开启自动校验自动校验功能默认是关闭的需要在buildSchema选项中设置validate: trueconst schema await buildSchema({ resolvers: [RecipeResolver], validate: true, // Enable class-validator integration });开启之后TypeGraphQL 会在解析器执行前根据输入/参数类的装饰器定义自动完成校验Resolver(of Recipe) export class RecipeResolver { Mutation(returns Recipe) async addRecipe(Arg(input) recipeInput: RecipeInput): PromiseRecipe { // 100% sure that the input is correct console.assert(recipeInput.title.length 30); console.assert(recipeInput.description.length 30); console.assert(recipeInput.description.length 255); } }也就是说你完全不需要在业务代码里手写任何if判断——校验失败会直接抛出异常永远不会进入解析器函数体。class-validator提供了远不止Length的大量校验装饰器如Min、Max、IsEmail、Matches等可按需查阅其官方文档。校验作用域全局开关与单参数开关自动校验默认开启后若某个场景确实不需要可以全局关闭const schema await buildSchema({ resolvers: [RecipeResolver], validate: false, // Disable automatic validation or pass the default config object });同时仍可在单个解析器参数上按需开启class RecipeResolver { Mutation(returns Recipe) async addRecipe(Arg(input, { validate: true }) recipeInput: RecipeInput) { // ... } }还可以直接传入ValidatorOptions对象例如使用 class-validator 的校验分组validation groups特性class RecipeResolver { Mutation(returns Recipe) async addRecipe( Arg(input, { validate: { groups: [admin] } }) recipeInput: RecipeInput, ) { // ... } }从源码看参数级配置与全局配置是合并处理的validateArg优先采用argValidateSettings若未设置则回退到globalValidateSettings当两者都是对象时会按全局配置为底、参数配置覆盖的方式展开合并见 validate-arg.ts。与 GraphQL 运行时职责的分工需要特别说明的是class-validator的若干默认行为被有意调整过skipMissingProperties默认被强制设为true只要用户没有显式设置为false因为 GraphQL 本身会独立检查参数/字段是否存在forbidUnknownValues默认被强制设为false只要用户没有显式设置为true因为 GraphQL 运行时会对 schema 未描述的额外数据自行把关。这两条逻辑体现在 validate-arg.ts 中。同理由于 GraphQL 会自行校验字段类型String、Int、Float、Boolean 等你完全不需要再使用IsOptional、Allow、IsString或IsInt这类装饰器它们与 GraphQL 的类型系统职责重叠。但是请注意当涉及嵌套输入或数组时必须显式使用ValidateNested()装饰器用于嵌套对象或{ each: true }选项用于数组嵌套校验才能正确生效。底层实现validateArg 如何工作在 src/resolvers/validate-arg.ts 中可以看到完整的校验流程优先取用validateFn参数级优先于全局见第 21 行若无自定义函数则根据validate配置判断是否执行校验同时对null与非对象值原始类型直接放行因为这类值由 GraphQL 类型系统把关shouldArgBeValidated第 9-10 行合并全局与参数级的ValidatorOptions并调整skipMissingProperties/forbidUnknownValues默认值通过动态import(class-validator)加载validateOrReject避免在未开启校验时强制引入该依赖对数组参数使用Promise.all对每个元素并行校验第 46-51 行校验失败时抛出ArgumentValidationError第 57 行。此外该校验过程是异步的函数返回Promise因此即使class-validator内部存在异步校验规则也能正常工作。客户端收到的错误响应当客户端发送非法数据时例如mutation ValidationMutation { addRecipe( input: { # Too long! title: Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet } ) { title creationDate } }TypeGraphQL 会抛出ArgumentValidationError其定义位于 src/errors/graphql/ArgumentValidationError.ts继承自GraphQLErrorextensions.code为BAD_USER_INPUT并携带validationErrors数组。默认情况下bootstrap 指南 中使用的apollo-server会将错误格式化为符合GraphQLFormattedError接口的结构。因此客户端收到的 JSON 中extensions.exception下会带有一个结构清晰的validationErrors属性{ errors: [ { message: Argument Validation Error, locations: [ { line: 2, column: 3 } ], path: [addRecipe], extensions: { code: INTERNAL_SERVER_ERROR, exception: { validationErrors: [ { target: { title: Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet }, value: Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet, property: title, children: [], constraints: { maxLength: title must be shorter than or equal to 30 characters } } ], stacktrace: [ Error: Argument Validation Error, at Object.anonymous (/type-graphql/src/resolvers/validate-arg.ts:29:11), at Generator.throw (anonymous), at rejected (/type-graphql/node_modules/tslib/tslib.js:105:69), at processTicksAndRejections (internal/process/next_tick.js:81:5) ] } } } ], data: null }注意上述响应中的INTERNAL_SERVER_ERROR是apollo-server对GraphQLError的默认包装结果你可以通过ApolloServer配置项中的formatError函数自定义实现将携带ValidationError数组的GraphQLError转换为期望的输出格式例如设置extensions.code ARGUMENT_VALIDATION_ERROR。自定义校验器Custom validator除了class-validatorTypeGraphQL 还允许接入其他校验库例如基于 Joi 装饰器的joiful或自研校验方案。接入方式非常简单只需要提供一个自定义函数它接收三个参数argValueArg()或Args()注入的参数值argType运行时类型信息例如String或RecipeInputresolverData解析器执行上下文泛型类型为ResolverDataTContext。该函数可以是异步函数当校验通过时应返回void即不返回任何值校验失败时应抛出错误。包装第三方库时务必遵循这个约定。在 buildSchema 中配置 validateFn将自定义函数作为validateFn选项传入buildSchema例如使用 joiful 的场景const schema await buildSchema({ // ... validateFn: argValue { // Call joiful validate const { error } joiful.validate(argValue); if (error) { // Throw error on failed validation throw error; } }, });对应的完整可运行示例位于仓库的 custom-validation 示例其中输入类通过 Joi 装饰器声明约束recipe.input.tsimport Joiful from joiful; import { Field, InputType } from type-graphql; InputType() export class RecipeInput implements PartialRecipe { Field() (Joiful.string().required().max(30)) title!: string; Field({ nullable: true }) (Joiful.string().min(30).max(255)) description?: string; }在单个参数上配置 validateFnvalidateFn同样支持作为Arg()或Args()装饰器的选项实现局部自定义校验Resolver() class SampleResolver { Query() sampleQuery( Arg(sampleArg, { validateFn: (argValue, argType) { // Do something here with arg value and type... }, }) sampleArg: string, ): string { // ... } }从源码validate-arg.ts可以确认参数级validateFn的优先级高于全局validateFnargValidateFn ?? globalValidateFn且一旦存在自定义函数整个class-validator流程会被完全跳过。⚠️ 注意使用自定义校验器时抛出的错误不会被包装成内置校验那样的ArgumentValidationError而是会原样向外传播需要自行在错误处理层处理。注意事项Caveats即使关闭校验功能也需要安装 class-validator即使向buildSchema传入{ validate: false }为了让应用能用tsc无错误编译仍需将class-validator作为 devDependency 安装。彻底移除 class-validator 的替代方案如果你希望完全从项目的node_modules中移除体积较大的class-validator可以在tsconfig.json中设置skipLibCheck: true从而抑制error TS2307: Cannot find module class-validator这一 TypeScript 编译错误。结合上文提到的动态导入实现validate-arg.ts未开启校验时该模块根本不会被实际加载。源码佐证与示例索引围绕本主题可以继续在仓库中查阅以下文件深入理解校验核心实现src/resolvers/validate-arg.ts校验错误类型src/errors/graphql/ArgumentValidationError.ts自动校验完整示例examples/automatic-validation/index.ts、examples/automatic-validation/recipe.input.ts、examples/automatic-validation/recipe.resolver.ts自定义校验joiful完整示例examples/custom-validation/index.ts、examples/custom-validation/recipe.input.ts校验相关功能测试tests/functional/validation.ts综上TypeGraphQL 把输入校验从手写样板代码中解放出来无论是开箱即用的class-validator自动校验还是按需接入的validateFn自定义方案都只需要少量配置即可与解析器生命周期无缝集成。你既可以直接开启全局校验快速落地也可以针对单个参数精细控制校验规则从而在保证数据质量的同时保持代码整洁。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐Valkey GLIDE vs 其他客户端基准测试揭示的性能差异与优势Valkey GLIDE vs 其他客户端基准测试揭示的性能差异与优势 Valkey GLIDE 是一款开源的 Valkey 客户端库支持 Valkey 以后端GraphQLAPI设计TypeGraphQL 参数与输入校验实战指南class-validator 自动验证与自定义 validateFn 完整解析TypeGraphQL 参数与输入校验实战指南class validator 自动验证与自定义 validateFn 完整解析 导读 GraphQL API后端GraphQLAPI设计TypeGraphQL 参数与输入校验Validation完整指南class-validator 集成与自定义校验器TypeGraphQL 参数与输入校验Validation完整指南class validator 集成与自定义校验器 TypeGraphQL 内置了参数后端GraphQLAPI设计上一篇发现突破性离线中文语音合成方案Android TTS引擎深度探索下一篇VM-UNet实战教程从安装到训练轻松搞定ISIC与Synapse数据集分割任务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表