- 后端
- GraphQL
- API设计
【免费下载链接】type-graphql
Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
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-metadatareflect-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!
相关推荐
TypeGraphQL入门指南:用TypeScript和装饰器构建GraphQL API
TypeGraphQL入门指南:用TypeScript和装饰器构建GraphQL API TypeGraphQL是一个革命性的开源库,通过TypeScript装
后端GraphQLAPI设计TypeGraphQL 入门:用 TypeScript 类与装饰器构建 GraphQL Schema 与 Resolver
TypeGraphQL 入门:用 TypeScript 类与装饰器构建 GraphQL Schema 与 Resolver TypeGraphQL 是一个面向
后端GraphQLAPI设计Nitro 资源系统完全指南:Public Assets、文件内联导入与 Server Assets
Nitro 资源系统完全指南:Public Assets、文件内联导入与 Server Assets Nitro 内置了一套完整的静态资源与文件资产管理方案,覆
后端GraphQLAPI设计
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考