
后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载TypeGraphQL 是一个面向 TypeScript Node.js 的 GraphQL 开发库核心思路是用普通 TypeScript 类和少量装饰器decorator声明式地定义 GraphQL schema从而消除 SDL 文件、TypeScript 接口与 Resolver 之间反复同步的样板代码。本文以 docs/introduction.md 为主线先剖析传统 GraphQL 开发的痛点再逐步演示对象类型、Resolver、输入校验与 schema 构建的完整流程并辅以本仓库源码如 ObjectType 装饰器、Field 装饰器、buildSchema说明底层原理读完后你将能够独立用 TypeGraphQL 搭建一个可运行、可校验、可鉴权的 GraphQL API。传统 TypeScript GraphQL 开发问题到底出在哪里GraphQL 本身非常优秀它解决了 REST API 常见的 overfetching过度获取与 underfetching获取不足问题。但在 Node.js 中用 TypeScript 开发 GraphQL API 时开发体验却常常“有点痛苦”。文档 docs/introduction.md 描述了一条典型的传统开发路径整个过程要横跨多套“语言”和多种文件先用 SDL 定义 schema 类型.graphql文件用 ORM 类定义数据模型例如 TypeORM 的实体类代表数据库中的表结构为 queries、mutations 和字段编写 resolver为所有参数、输入和对象类型手写 TypeScript 接口最终实现 resolver并写出一长串泛型签名export const getRecipesResolver: GraphQLFieldResolvervoid, Context, GetRecipesArgs async ( _, args, ctx, ) { // Common tasks repeatable for almost every resolver const auth Container.get(AuthService); if (!auth.check(ctx.user)) { throw new NotAuthorizedError(); } await joi.validate(getRecipesSchema, args); const repository TypeORM.getRepository(Recipe); // Business logic, e.g.: return repository.find({ skip: args.offset, take: args.limit }); };从源码结构看这类写法最大的隐患是代码冗余与多份“真相”难以同步。每给实体新增一个字段都要按顺序修改多处文件修改 ORM 实体类修改 SDL 中的 schema 类型更新对应的 TypeScript 接口必要时同步更新校验规则如上例中的joi.validate。任何一处遗漏或类型失误都会造成 schema、类型与实现不一致。同时字段名拼写错误不会被编译器捕获IDE 的“重命名F2”功能也无法跨文件正确工作——因为同一字段在多处重复声明编辑器无法识别它们之间的关联。TypeGraphQL 的核心思想单一事实来源Single Source of TruthTypeGraphQL 的设计目标正是消除上述痛点。它的核心主张是只用一个真相来源——用 TypeScript 类和少量装饰器定义 schema其余的一切SDL、接口、校验、鉴权都从这个类自动推导或就近声明。ObjectType() class Recipe { Field() title: string; Field(type [Rate]) ratings: Rate[]; Field({ nullable: true }) averageRating?: number; }在这段代码中ObjectType()把Recipe类标记为 GraphQL 的 object typeField()声明类属性将映射为 GraphQL 字段Field(type [Rate])声明一个Rate数组类型Field({ nullable: true })声明可空字段。文档强调TypeGraphQL 还内置了一批实用能力validation校验、authorization鉴权和 dependency injection依赖注入把过去每个 resolver 里都要手工重复的样板任务取用户、查权限、校验参数、查仓储收编为框架级机制。装饰器背后的源码实现从本仓库源码可以印证这套“类即 schema”的机制ObjectType.ts 支持三种调用形态无参、options 对象、名称 options最终通过getMetadataStorage().collectObjectMetadata(...)把类名、描述、实现的接口类等元数据收集起来Field.ts 会读取 TypeScript 反射元数据design:type或design:returntype配合findType推断字段类型再通过collectClassFieldMetadata登记字段名、schema 名、可空性、描述与废弃原因等所有装饰器收集到的元数据最终统一汇入 MetadataStorage内部维护queries、mutations、objectTypes、fieldResolvers等数组供 SchemaGenerator 在构建 schema 时消费。因此TypeGraphQL 运行时并不维护“SDL 文件 接口”两份定义schema 完全由这些类与装饰器元数据生成。用类定义对象类型ObjectType 与 Field 的完整用法为了直观体验我们以文档 docs/getting-started.md 中的“食谱RecipeAPI”为例。目标是在 SDL 中得到如下类型type Recipe { id: ID! title: String! description: String creationDate: Date! ingredients: [String!]! }首先定义纯 TypeScript 类只写属性与类型class Recipe { id: string; title: string; description?: string; creationDate: Date; ingredients: string[]; }然后加上装饰器把类“翻译”成 GraphQL 类型ObjectType() class Recipe { Field(type ID) id: string; Field() title: string; Field({ nullable: true }) description?: string; Field() creationDate: Date; Field(type [String]) ingredients: string[]; }几个关键语法点对应 types-and-fields.md 中的完整规则简单类型string、boolean、Date直接Field()即可TypeScript 反射元数据足够数组/泛型类型受 TypeScript 反射能力限制必须显式用箭头函数标注如Field(type [String])嵌套数组用[[Int]]表示深度为 2 的整数数组为什么用函数而非{ type: Rate }对象函数写法type [Rate]能够规避循环依赖问题如Post -- User相互引用因此成为约定想少敲键盘可以用简写Field(() Rate)可空性默认所有字段非空与 TS 属性语义一致。可空属性需同时满足两个条件类属性上加?装饰器传{ nullable: true }若要整个 schema 默认可空可在buildSchema中设置nullableByDefault: true详见 bootstrap.md列表的精细可空性{ nullable: true | false }只作用于整个列表[Item!]或[Item!]!需要稀疏数组时用nullable: items产出[Item]!或nullable: itemsAndList产出[Item]字段选项Field还支持nameschema 中的字段名、description、deprecationReason、complexity等高级选项详见 Field.ts 中FieldOptions的定义。仓库中的真实示例 examples/simple-usage/recipe.type.ts 展示了更丰富的用法用ObjectType({ description: ... })给类型加描述、用 getter 映射计算字段如averageRating、用deprecationReason标记废弃字段等。编写 ResolverResolver、Query 与 Mutation类型定义好之后下一步是创建 resolvercontroller类来承载查询与变更逻辑。以下示例来自 getting-started.md构造函数中注入RecipeServiceResolver(Recipe) class RecipeResolver { constructor(private recipeService: RecipeService) {} Query(returns Recipe) async recipe(Arg(id) id: string) { const recipe await this.recipeService.findById(id); if (recipe undefined) { throw new RecipeNotFoundError(id); } return recipe; } Query(returns [Recipe]) recipes(Args() { skip, take }: RecipesArgs) { return this.recipeService.findAll({ skip, take }); } Mutation(returns Recipe) Authorized() addRecipe( Arg(newRecipeData) newRecipeData: NewRecipeInput, Ctx(user) user: User, ): PromiseRecipe { return this.recipeService.addNew({ data: newRecipeData, user }); } Mutation(returns Boolean) Authorized(Roles.Admin) async removeRecipe(Arg(id) id: string) { try { await this.recipeService.removeById(id); return true; } catch { return false; } } }要点说明Resolver(Recipe)声明该 resolver 服务于Recipe类型从源码看Resolver.ts 会把 resolver 类注册到元数据存储中并解析目标 object typeQuery/Mutation分别对应 GraphQL 的 query 与 mutation 根字段其实现Query.ts通过getResolverMetadata收集返回类型与选项然后调用collectQueryHandlerMetadata登记参数装饰器分工明确Arg(id)取单个参数、Args()展开一组参数、Ctx(user)取上下文对象returns Recipe函数式返回类型声明与Field(type [Rate])同理既用于推断泛型返回类型也用于规避循环依赖具体规则见 resolvers.mdAuthorized()与Authorized(Roles.Admin)分别表示“仅登录用户可访问”与“满足指定角色才可访问”。源码层面Authorized.ts 支持零参数、角色数组与可变参数三种形态会把角色元数据登记到类或字段上运行时鉴权逻辑由 authChecker 统一执行。实际项目中还需要配套实现RecipeService业务层与RecipeNotFoundError自定义错误。完整的可运行版本可参考 examples/simple-usage/recipe.resolver.ts——它实现了ResolverInterfaceRecipe、使用FieldResolver处理派生字段如按最低评分过滤后的ratingsCount并配合Root()访问父级对象。输入类型与参数InputType、ArgsType 与自动校验上文用到的NewRecipeInput与RecipesArgs同样是类只是用不同的装饰器标注InputType() class NewRecipeInput { Field() MaxLength(30) title: string; Field({ nullable: true }) Length(30, 255) description?: string; Field(type [String]) ArrayMaxSize(30) ingredients: string[]; } ArgsType() class RecipesArgs { Field(type Int) Min(0) skip: number 0; Field(type Int) Min(1) Max(50) take: number 25; }两个关键设计类型分工InputType()用于 mutation/query 的输入对象GraphQLinputArgsType()用于一组参数会被展开为多个独立参数。两者都用Field声明字段因此天然复用对象类型的字段声明语法。声明式校验Length、Min、Max、ArrayMaxSize等来自class-validator库的装饰器。TypeGraphQL 会在运行时自动执行这些校验无需像传统写法那样手工调用joi.validate(...)。本仓库 package.json 将class-validator声明为可选依赖0.14.3并依赖class-transformer完成输入实例化。仓库 examples/simple-usage/recipe.input.ts 给出了最小输入类型示例完整的自动校验配置、自定义验证器validate函数以及ValidateArgs选项可参见 validation.md。构建 SchemabuildSchema 与 Schema Generator所有类型、resolver、输入类定义完毕后最后一步是把它们交给buildSchema生成可执行的 GraphQL schemaconst schema await buildSchema({ resolvers: [RecipeResolver], }); // ... Server源码 buildSchema.ts 展示了这一函数的行为resolvers是必填的非空数组NonEmptyArrayFunction空数组会直接抛出 “Emptyresolversarray property found inbuildSchemaoptions!” 错误内部调用SchemaGenerator.generateFromMetadata(...)把所有装饰器收集的元数据转化为真正的GraphQLSchema支持emitSchemaFile选项字符串路径 / 配置对象 /true可将生成的 SDL 写入文件默认./schema.graphql方便对比与审查另有同步版本buildSchemaSync适用于非异步场景。以本文的 Recipe 示例为例打印出的 schema 大致如下type Recipe { id: ID! title: String! description: String creationDate: Date! ingredients: [String!]! } input NewRecipeInput { title: String! description: String ingredients: [String!]! } type Query { recipe(id: ID!): Recipe recipes(skip: Int 0, take: Int 25): [Recipe!]! } type Mutation { addRecipe(newRecipeData: NewRecipeInput!): Recipe! removeRecipe(id: ID!): Boolean! }注意recipes查询中skip、take的默认值0 与 25正是来自RecipesArgs类的属性初始化器——这印证了“类即 schema”的单一真相来源设计。把 schema 接入 HTTP 服务器的完整流程可参考 examples/simple-usage/index.ts先import reflect-metadata再用buildSchema({ resolvers, emitSchemaFile })构建最后交给ApolloServer并通过startStandaloneServer监听 4000 端口启动。不止于此接口、枚举、联合类型与更多高级能力如 introduction.md 结尾所述上面的例子只是冰山一角。TypeGraphQL 对 GraphQL 的完整类型体系都有支持接口InterfaceInterfaceType()implements配合类继承见 interfaces.md 与 inheritance.md枚举EnumregisterEnumType把 TS 枚举注册为 GraphQL 枚举见 enums.md联合类型UnioncreateUnionType声明多类型联合并自定义resolveType见 unions.md自定义标量ScalarScalar装饰器注册GraphQLScalarType或直接复用graphql-scalars等第三方实现见 scalars.md字段级 ResolverFieldResolver在 examples/simple-usage/recipe.resolver.ts 中有现成案例鉴权检查器通过authChecker自定义授权逻辑仓库测试 tests/functional/authorization.ts 覆盖了多种角色组合场景依赖注入与 TypeDI、tsyringe 等容器集成见 dependency-injection.md 与 examples 中的 tsyringe、using-container 示例ORM 集成TypeORM、MikroORM、Typegoose 等都有配套示例见 examples.md。如何开始参照 installation.md 安装type-graphql、graphql、reflect-metadata与class-validator如需校验在入口文件顶部import reflect-metadata并在tsconfig.json中开启emitDecoratorMetadata与experimentalDecoratorsesm场景的配置详见 esm.md按本文顺序定义ObjectType类型、Resolver类、InputType/ArgsType输入类调用buildSchema({ resolvers })得到 schema接入 Apollo Server / Express / Fastify 等任意 HTTP 层可运行 examples/simple-usage 目录中的示例npm run example:simple-usage具体脚本见 package.json验证完整链路。小结本文梳理了 TypeGraphQL 解决的核心问题——传统 TS GraphQL 开发中 SDL、接口、校验、鉴权与业务逻辑多份定义难以同步的冗余之痛——并沿着“对象类型 → Resolver → 输入与校验 → schema 构建”这条主线完整还原了食谱 API 的构建过程。借助ObjectType、Field、Query、Mutation、InputType、ArgsType、Authorized等装饰器配合buildSchema生成可执行 schema所有定义都能收敛到 TypeScript 类这一个真相来源让字段重命名、类型检查、输入校验与权限控制获得编译期与运行期的双重保障。更高级的接口、枚举、联合类型、自定义标量、字段解析器与 ORM 集成都可以在此基础上按需查阅本仓库 docs 目录下的对应指南逐步深入。赞分享后端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 与 ResolverTypeGraphQL 入门指南用 TypeScript 类与装饰器声明式构建 GraphQL Schema 与 Resolver 导读 TypeGraphQ后端GraphQLAPI设计LaTeX公式秒变Word格式告别复制粘贴的烦恼让数学表达更自由LaTeX公式秒变Word格式告别复制粘贴的烦恼让数学表达更自由 还在为学术论文中复杂的数学公式而头疼吗每次从网页复制LaTeX公式到Word结果总是一后端GraphQLAPI设计TypeGraphQL 入门实战用 TypeScript 类与装饰器构建完整 GraphQL SchemaGetting Started 全解TypeGraphQL 入门实战用 TypeScript 类与装饰器构建完整 GraphQL SchemaGetting Started 全解 本篇指南基后端GraphQLAPI设计上一篇艾尔登法环帧率解锁EldenRingFpsUnlockAndMore三问三答把144Hz真正用起来下一篇免费开源自托管游戏串流服务器 Sunshine让一台高性能 PC 喂饱全家所有屏幕创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考