☰
TypeGraphQL 入门指南:用 TypeScript 类与装饰器构建一个食谱 GraphQL API
2026/9/27 21:40:51 网站建设 项目流程
  • 后端
  • GraphQL
  • API设计

【免费下载链接】type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!

项目地址:https://gitcode.com/gh_mirrors/ty/type-graphql
点击查看免费下载

TypeGraphQL 的核心思路是用 TypeScript 的类(class)和装饰器(decorator)声明式地定义 GraphQL 类型、查询(Query)、变更(Mutation)与输入参数,从而让 Schema 定义与业务代码完全同构。本文以官方入门文档(website/versioned_docs/version-0.17.1/getting-started.md,与 docs/getting-started.md 内容一致)为主线,从零构建一个"烹饪食谱"示例 API,完整覆盖@ObjectType、@Field、@Resolver、@Query、@Mutation、@Arg、@InputType、@ArgsType以及buildSchema的完整链路,并结合仓库源码(src/、examples/simple-usage/)讲解底层原理。读完本文,你将掌握用 TypeGraphQL 写出一个可运行、可校验、带权限控制的完整 GraphQL 服务的全部基本步骤。

前置准备:环境与 TypeScript 配置

本文假设你已经完成了 安装指南 中的全部步骤。

安装 TypeGraphQL 需要三样东西:主包type-graphql、其 peer 依赖graphql(GraphQL 官方 JS 实现)及类型定义,以及让装饰器类型反射生效的reflect-metadatashim:

npm i graphql @types/graphql type-graphql npm i reflect-metadata

reflect-metadata必须在入口文件的最顶部导入(早于任何type-graphql或 resolver 的 import),否则装饰器拿不到design:type等元数据:

import "reflect-metadata";

tsconfig.json中必须开启两个关键选项,并配合 ES2016 目标:

{ "compilerOptions": { "target": "es2016", "module": "commonjs", "lib": ["es2016", "esnext.asynciterable"], "experimentalDecorators": true, "emitDecoratorMetadata": true } }

其中experimentalDecorators启用装饰器语法,emitDecoratorMetadata让 TypeScript 在编译期把属性/返回值的类型信息写入元数据——这是 TypeGraphQL 在未显式给出类型函数时推断类型的根基。esnext.asynciterable是订阅功能依赖AsyncIterator时所需的 lib 项。

Types:用@ObjectType与@Field定义 Recipe 类型

我们的目标是得到如下 SDL 描述的Recipe类型:

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[]; }

然后为类和每个属性加上装饰器,TypeGraphQL 会据此生成对应的 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[]; }

关键点拆解:

  • @ObjectType()标记一个类为 GraphQL Object Type。从源码看,ObjectType.ts 支持无参、传选项对象或传自定义名称(name)三种重载,还支持description、implements(接口实现)等选项。
  • @Field()将类属性暴露为 GraphQL 字段。默认情况下,类型由 TypeScript 反射(design:type)推断:string→String、number→Float、boolean→Boolean、Date→ 内置的Date标量。
  • type => ID和type => [String]是"返回类型函数",用于指定ID标量与数组类型。type => [String]生成的是[String!]!——元素非空、数组本身也非空,这正是文档目标 SDL 中ingredients: [String!]!的来源。
  • { nullable: true }控制可空性:description?: string配合nullable: true生成description: String。

nullable、array等的完整规则详见 fields and types docs。在仓库中,examples/simple-usage/recipe.type.ts 给出了一个更丰富的实践版本,它同时演示了description、deprecationReason(字段废弃提示)、基于 getter 的计算字段(averageRating),以及Float/Int标量映射。

Resolvers:用@Resolver组织查询与变更

类型定义好后,创建 resolver(可类比控制器/controller)类。它通过构造函数注入RecipeService,并提供典型的 CRUD 查询与变更:

@Resolver(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, ): Promise<Recipe> { 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 支持无参、传类、传返回类型函数三种形式;若不传任何类型,在构建 schema 时会抛出 "No provided object type" 错误。
  • @Query(returns => Recipe)定义 GraphQL 查询。returns => ...返回类型函数在这里是必需的,因为方法的返回类型无法通过反射可靠推断(如Promise<Recipe>)。源码 Query.ts 会把方法元数据收集进Queryhandler 列表。
  • @Arg("id")声明单个查询参数,@Args()则将一组参数合并为一个对象(见下文RecipesArgs)。
  • @Ctx("user")从上下文(context)中取出user对象,用于获取当前请求的用户身份。
  • @Authorized()与@Authorized(Roles.Admin)是权限装饰器:无参表示仅限已认证用户,带角色参数则进一步要求满足角色条件。从源码 Authorized.ts 看,它可以作用在 resolver 类、方法或字段上,并把roles收集为授权元数据,最终由用户提供的 auth checker 消费(详见 authorization 文档)。

returns => Recipe为何必须写成函数、何时省略等细节,见 resolvers docs。

Inputs 与 Arguments:@InputType、@ArgsType与自动校验

NewRecipeInput和RecipesArgs同样是普通的类,只不过分别用@InputType()与@ArgsType()标记,并叠加class-validator的校验装饰器:

@InputType() class NewRecipeDataInput { @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()将类映射为 GraphQL input 类型;@ArgsType()将类映射为一组参数(每个字段成为一个独立参数,而不是一个嵌套对象),因此recipes(skip: Int, take: Int)在 SDL 中是平铺的参数。
  • @Length、@Min、@Max、@ArrayMaxSize都来自class-validator库(MaxLength限制字符串最大长度,Length(30, 255)限制 30~255 字符,Min/Max限制数值范围,ArrayMaxSize限制数组元素个数上限)。TypeGraphQL 会自动为@InputType与@ArgsType的字段执行这些校验,校验失败会抛出ArgumentValidationError(见 ArgumentValidationError.ts)。
  • 需要注意命名细节:入门文档早期版本写作@MaxArraySize(30),而 class-validator 中该装饰器的规范名称是@ArrayMaxSize(30),当前仓库 docs/getting-started.md 已采用正确写法。
  • 字段默认值(skip: number = 0、take: number = 25)会被反映到生成的 SDL 中(skip: Int = 0、take: Int = 25)。
  • 一个小提示:原示例中@InputType()类名为NewRecipeDataInput,而 resolver 与最终 SDL 中使用NewRecipeInput。默认情况下 GraphQL 输入类型名取自类名(可推断该差异来自示例的命名不一致),若希望固定输出NewRecipeInput,可向@InputType传入name参数,如@InputType("NewRecipeInput")。

构建 Schema:buildSchema与emitSchemaFile

最后一步是把以上所有装饰器元数据"编译"为可执行的 GraphQL schema,使用buildSchema:

const schema = await buildSchema({ resolvers: [RecipeResolver], }); // ...creating express server or sth

从源码 buildSchema.ts 可以看到:

  • buildSchema接收resolvers(非空 resolver 类数组)并交给SchemaGenerator.generateFromMetadata生成GraphQLSchema;若传入空数组会抛出Empty resolvers array property found in buildSchema options错误。
  • 它还支持emitSchemaFile选项:传字符串路径、布尔值或配置对象,把打印出的 SDL 写入文件(默认路径为进程工作目录下的schema.graphql,见 getEmitSchemaDefinitionFileOptions)。参考 examples/simple-usage/index.ts:
const schema = await buildSchema({ resolvers: [RecipeResolver], emitSchemaFile: path.resolve(__dirname, "schema.graphql"), });
  • 同时提供同步版本buildSchemaSync(见 buildSchema.ts),便于在无异步上下文的场景使用。

构建完成后,打印出的 SDL 正是我们期望的样子:

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! }

注意两处自动推导出的细节:recipe(id: ID!): Recipe的返回类型是可空的(单个查询可能找不到数据,方法返回Promise<Recipe>但查询结果允许 null),而recipes与removeRecipe的结果是非空数组/非空标量。这些可空性规则由returns => ...与{ nullable }选项共同决定。

在仓库中查看完整可运行示例

examples/simple-usage/目录提供了与入门指南同主题的完整可运行版本:

  • examples/simple-usage/index.ts:bootstrap函数中buildSchema+ Apollo Server 启动,监听 4000 端口,并把 SDL 输出到schema.graphql;
  • examples/simple-usage/recipe.type.ts:带description、deprecationReason、getter 计算字段的RecipeObject Type;
  • examples/simple-usage/recipe.resolver.ts:实现ResolverInterface<Recipe>,包含recipe/recipes查询、addRecipe变更及一个带@Arg默认值的@FieldResolver(ratingsCount(minRate));
  • examples/simple-usage/recipe.input.ts:@InputType输入类;
  • examples/simple-usage/recipe.data.ts:内存中的样例数据工厂。

更进一步

入门指南只是冰山一角:接口(interfaces)、枚举(enums)、联合类型(unions)、自定义标量(custom scalars)TypeGraphQL 都完整支持,此外还有授权检查器(auth checker)、继承(inheritance)、字段解析器(field resolvers)、订阅(subscriptions)、依赖注入(DI container)、middleware 与查询复杂度限制等进阶能力。更多完整用例可前往 Examples 章节,例如其中展示了 TypeGraphQL 与 TypeORM 的集成方式,以及各能力的配套示例目录(examples/下按主题组织,如authorization/、interfaces-inheritance/、redis-subscriptions/、query-complexity/等),方便对照学习。

  • 后端
  • GraphQL
  • API设计

【免费下载链接】type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!

项目地址:https://gitcode.com/gh_mirrors/ty/type-graphql
点击查看免费下载
上一篇:SillyTavern终极指南:5个简单技巧打造生动AI角色卡片系统
下一篇:像素字体新选择:Fusion Pixel Font 让你的设计瞬间回到80年代

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询