- 后端
- GraphQL
- API设计
【免费下载链接】type-graphql
Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
TypeGraphQL 的核心能力是通过 TypeScript 类和装饰器直接生成 GraphQL schema,而无需手写 SDL。但在很多真实场景下,我们仍然需要把 schema 打印成schema.graphql(旧版本为schema.gql)文本文件——比如供 GraphQL 生态中的客户端工具做查询自动补全与校验、作为回归检测的快照、或者让团队成员直接阅读 SDL 来探索 API。本文以 TypeGraphQL 官方文档为基础,结合仓库源码与测试用例,系统讲解两种输出 schema 定义文件的完整方案:buildSchema的emitSchemaFile自动生成,以及emitSchemaDefinitionFile/emitSchemaDefinitionFileSync的程序化生成,并深入剖析底层实现细节。
为什么要输出 Schema SDL 文件
TypeGraphQL 的主打特性是"只用类与装饰器建 schema",因此生成的 schema 对象通常只存在于运行时内存中。但以下场景需要它被持久化为 SDL 文本文件:
- 客户端工具链:GraphQL 生态中的很多工具需要 SDL 文件来完成客户端查询的自动补全与校验;
- Schema 回归检测:把 SDL 文件当作快照(snapshot),通过 diff 感知 schema 的意外变更;
- API 探索:相比阅读复杂的 TypeGraphQL 应用代码、或在 GraphiQL / GraphQL Playground 中反复点击,直接阅读 SDL 文件往往更直观高效。
TypeGraphQL 为此提供了两种生成 schema 定义文件的方式,下文分别展开。值得注意的是,0.17.0 时代默认输出的文件名是schema.gql,而当前仓库版本(对应 docs/emit-schema.md)中默认文件名已统一为schema.graphql,下文以当前仓库行为为准。
方式一:通过 buildSchema 的 emitSchemaFile 选项自动生成
最省事的方式是在调用buildSchema时传入emitSchemaFile选项,让 TypeGraphQL 在每次构建 schema 时自动把定义写入文件。该选项支持三种形态:布尔值、字符串路径、以及配置对象。
const schema = await buildSchema({ resolvers: [ExampleResolver], // 自动在项目工作目录下创建 schema.graphql 文件 emitSchemaFile: true, // 或者指定文件写入路径 emitSchemaFile: path.resolve(__dirname, "__snapshots__/schema/schema.graphql"), // 或者传入配置对象,精细化控制输出 emitSchemaFile: { path: __dirname + "/schema.graphql", sortedSchema: false, // 默认情况下输出的 schema 会按字母序排序 }, });三种传参形态的语义
从 src/utils/buildSchema.ts 的getEmitSchemaDefinitionFileOptions实现可以精确还原三种形态的处理逻辑:
emitSchemaFile: true:使用默认路径path.resolve(process.cwd(), "schema.graphql"),即当前进程工作目录(process.cwd())下的schema.graphql;emitSchemaFile: "路径字符串":把字符串直接当作完整的目标文件路径(包含文件名),示例中的__snapshots__/schema/schema.graphql即属此类;emitSchemaFile: { ... }(配置对象):对象类型为EmitSchemaFileOptions,即{ path?: string } & Partial<PrintSchemaOptions>。其中path缺省时回落为默认路径,其余属性(即PrintSchemaOptions的字段)会与默认值做浅合并({ ...defaultPrintSchemaOptions, ...options })。
PrintSchemaOptions:控制 schema 输出的格式
PrintSchemaOptions是控制输出格式的配置接口,定义于 src/utils/emitSchemaDefinitionFile.ts:
export interface PrintSchemaOptions { sortedSchema: boolean; } export const defaultPrintSchemaOptions: PrintSchemaOptions = { sortedSchema: true, };sortedSchema(默认true):决定打印前是否对 schema 做字典序排序。排序通过graphql-js的lexicographicSortSchema实现(见同文件getSchemaFileContent),使类型、字段按字母序稳定排列,利于生成 diff 友好的快照文件;设为false则保留 schema 构建时的原始定义顺序。- 0.17.0 旧版文档中展示的
commentDescriptions: true选项(把"""..."""描述输出为#注释形式)在旧版PrintSchemaOptions中存在,当前仓库版本的选项接口已收敛为sortedSchema一个字段,使用时以当前安装版本导出的类型为准。
自动生成的文件头部警告
通过emitSchemaFile或emitSchemaDefinitionFile生成的文件并非纯 SDL,而是带有一段固定的生成警告头(generatedSchemaWarning,定义于 src/utils/emitSchemaDefinitionFile.ts):
# ----------------------------------------------- # !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! # !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! # -----------------------------------------------这提醒开发者该文件是构建产物、不应手工修改。测试 tests/functional/emit-schema-sdl.ts 中的checkSchemaSDL也明确断言生成内容必须包含"THIS FILE WAS GENERATED"字样。
路径不存在时自动创建目录
emitSchemaFile指向的目录不存在时,TypeGraphQL 不会报错,而是自动递归创建目录。其底层由 src/helpers/filesystem.ts 的outputFile/outputFileSync完成:先尝试直接写文件,若抛出ENOENT(目录不存在)则先用mkdir(dirname, { recursive: true })建目录再写入;其他异常则原样向上抛出。这也解释了为何示例中__snapshots__/schema/schema.graphql这样的深层路径可以一次成功。
buildSchemaSync 同步版本
如果项目环境不适合异步构建(例如某些启动脚本或同步初始化流程),可以使用buildSchemaSync。它与buildSchema接受完全相同的BuildSchemaOptions,包括emitSchemaFile的三种形态,内部调用emitSchemaDefinitionFileSync同步写盘,见 src/utils/buildSchema.ts。异步/同步两种 API 由emitSchemaDefinitionFile(基于fs/promises)与emitSchemaDefinitionFileSync(基于fs)分别支撑。
方式二:程序化调用 emitSchemaDefinitionFile 手动生成
第二种方式完全绕开buildSchema,在任何持有GraphQLSchema对象的地方手动调用导出函数写文件。TypeGraphQL 从 src/utils/index.ts 导出emitSchemaDefinitionFile、emitSchemaDefinitionFileSync以及PrintSchemaOptions类型、defaultPrintSchemaOptions常量。
import { emitSchemaDefinitionFile } from "type-graphql"; // ... hypotheticalFileWatcher.watch("./src/**/*.{resolver,type,input,arg}.ts", async () => { const schema = getSchemaNotFromBuildSchemaFunction(); await emitSchemaDefinitionFile("/path/to/folder/schema.graphql", schema); });函数签名(见 src/utils/emitSchemaDefinitionFile.ts):
export function emitSchemaDefinitionFileSync( schemaFilePath: string, schema: GraphQLSchema, options: PrintSchemaOptions = defaultPrintSchemaOptions, ): void; export async function emitSchemaDefinitionFile( schemaFilePath: string, schema: GraphQLSchema, options: PrintSchemaOptions = defaultPrintSchemaOptions, ): Promise<void>;- 第一个参数为完整目标文件路径(含文件名);
- 第二个参数为任意
GraphQLSchema对象,不要求它一定来自buildSchema(上例中的getSchemaNotFromBuildSchemaFunction即示意任意来源); - 第三个可选参数为
PrintSchemaOptions,省略时使用defaultPrintSchemaOptions(即sortedSchema: true)。
典型应用场景
官方文档点名的两类典型用法:
- 快照测试:把该函数放进测试脚本,生成 schema 快照并与预期文件比对,从而在 schema 发生意外变化时让测试失败;
- 本地开发热生成:结合文件监听器(如上例的
hypotheticalFileWatcher),在.ts源文件变更时自动重新生成 SDL,保持本地随时有一份最新 schema 可读。
进阶:让自定义指令出现在生成的 SDL 中
TypeGraphQL 本身并不直接支持在输出的 schema 中携带自定义指令(custom directives),原因是graphql-js的printSchema函数存在限制,无法打印指令定义。如果你需要自定义指令出现在生成文件中,就需要自行实现一个输出函数,借助第三方printSchema实现(例如@graphql-tools/utils提供的printSchemaWithDirectives)。这一主题完整收录于当前版本文档 docs/emit-schema.md,实现示例:
import { GraphQLSchema, lexicographicSortSchema } from "graphql"; import { printSchemaWithDirectives } from "@graphql-tools/utils"; import fs from "node:fs/promises"; export async function emitSchemaDefinitionWithDirectivesFile( schemaFilePath: string, schema: GraphQLSchema, ): Promise<void> { const schemaFileContent = printSchemaWithDirectives(lexicographicSortSchema(schema)); await fs.writeFile(schemaFilePath, schemaFileContent); }用法与标准emitSchemaDefinitionFile完全一致:
const schema = await buildSchema(/*...*/); await emitSchemaDefinitionWithDirectivesFile("/path/to/folder/schema.graphql", schema);自定义函数可以同时复用 TypeGraphQL 的lexicographicSortSchema排序思路,保持输出稳定。若无需自定义指令,则优先使用内建的emitSchemaDefinitionFile即可。
测试与真实项目中的用法参考
仓库中的功能测试 tests/functional/emit-schema-sdl.ts 完整覆盖了上述全部行为,可作为实现细节的权威佐证:
- 默认路径:mock
process.cwd()后,emitSchemaFile: true会在工作目录生成schema.graphql(测试第 168-177 行); - 路径字符串:
emitSchemaFile: targetPath直接写入指定路径(测试第 158-166 行); - 配置对象:
emitSchemaFile: { path, sortedSchema: false }同时生效(测试第 179-192 行);传空对象{}时回落默认路径与默认排序(测试第 194-205 行); - 排序行为:
checkSchemaSDL断言sortedSchema: true时descriptionProperty排在normalProperty之前(字母序),false时保持定义顺序(测试第 57-69 行); - 错误传播:写入或建目录遇到非
ENOENT异常时,错误会原样抛出(测试第 89-113、134-154 行); - 同步版本:
buildSchemaSync与emitSchemaDefinitionFileSync的行为逐项等价(测试第 208-257 行)。
在真实项目中,emitSchemaFile常与运行环境联动。例如 docs/azure-functions.md 展示了按环境变量条件开启的做法:
emitSchemaFile: process.env.NODE_ENV === "local" ? path.resolve("./src/schema.graphql") : false,这样在本地开发时自动产出 schema 文件,而在云端运行时关闭以免写只读文件系统。另一个例子是 docs/nestjs.md,在 NestJS 集成中同样通过emitSchemaFile: true便捷生成 SDL。这两处都是"自动生成"方式在实际工程中的典型落地形态。
小结
- 自动生成:
buildSchema({ emitSchemaFile: true | 路径 | { path?, sortedSchema? } }),构建 schema 的同时写盘,默认输出到process.cwd()/schema.graphql,默认按字典序排序,并自动附带"由 TypeGraphQL 生成"的警告头、自动创建缺失目录;同步场景可用buildSchemaSync。 - 程序化生成:
emitSchemaDefinitionFile(path, schema, options?)与同步版emitSchemaDefinitionFileSync,适合快照测试、文件监听热更新等需要掌控时机的场景,schema 对象可来自任意来源。 - 自定义指令:内建输出基于
printSchema无法打印指令定义,需要自定义输出函数(如借助printSchemaWithDirectives)后以相同方式调用。
两种方式均以 src/utils/emitSchemaDefinitionFile.ts 为统一实现核心,文件写入细节封装在 src/helpers/filesystem.ts,完整行为由 tests/functional/emit-schema-sdl.ts 验证,可按需深入源码进一步探索。
- 后端
- GraphQL
- API设计
【免费下载链接】type-graphql
Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
相关推荐
TypeGraphQL 输出 Schema SDL:从 buildSchema 自动生成到程序化导出与自定义指令
TypeGraphQL 输出 Schema SDL:从 buildSchema 自动生成到程序化导出与自定义指令 TypeGraphQL 的核心特性是仅凭 Ty
后端GraphQLAPI设计TypeGraphQL Schema SDL 生成指南:用 buildSchema 与 emitSchemaDefinitionFile 将 GraphQL Schema 导出为文件
TypeGraphQL Schema SDL 生成指南:用 buildSchema 与 emitSchemaDefinitionFile 将 GraphQL S
后端GraphQLAPI设计TypeGraphQL 输出 Schema SDL 文件全指南:从 `emitSchemaFile` 到程序化导出与自定义指令
TypeGraphQL 输出 Schema SDL 文件全指南:从 emitSchemaFile 到程序化导出与自定义指令 导读 TypeGraphQL 的核心
后端GraphQLAPI设计
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考