后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载本指南完整讲解 TypeGraphQLtype-graphql中联合类型Union Types的定义、使用与运行时类型解析。你将学会用createUnionType把多个ObjectType类组合成一个 GraphQL Union在Query/Mutation/ 字段返回值中使用它并掌握默认的instanceof判定与自定义resolveType两种类型解析机制最终能构建类似“电影 / 演员混合搜索结果”的真实查询场景。为什么需要 Union 类型GraphQL 的 Schema 通常是强类型的但有些 API 场景需要“模糊返回”查询的结果不是一个确定的具体类型而是多个可能类型中的某一个。典型例子是电影网站的全站搜索——输入一个短语同时搜索电影Movie和演员Actor那么查询的返回类型既可能是Movie也可能是Actor。面对这种需求可以使用Union Type联合类型声明“返回值要么是Movie要么是Actor”由客户端用内联片段inline fragment按需取字段使用Interface接口类型声明一组类型共享的公共字段适合所有成员都具备同构结构的情形返回一个包装类型如SearchResult包一个type字段但类型安全性和可扩展性都不如 Union。TypeGraphQL 同时支持 Union 与 接口与继承两者的选择取决于业务成员类型字段差异较大、无公共字段时Union 更合适成员存在公共契约时Interface 更合适。GraphQL 官方的 Union 类型定义可参考 官方 GraphQL 文档本文不再展开协议细节聚焦 TypeGraphQL 的落地用法。定义 Union从 ObjectType 到 createUnionType第一步准备两个对象类型类Union 的成员必须先是普通的ObjectType()类。沿用上文“电影 / 演员”的例子import { Field, Int, ObjectType } from type-graphql; ObjectType() class Movie { Field() name: string; Field() rating: number; } ObjectType() class Actor { Field() name: string; Field(type Int) age: number; }注意Actor.age显式声明为 GraphQL 的Int因为 TypeScript 的number默认会被映射为FloatMovie.rating未显式标注则会按默认标量映射。关于标量映射与类型推断可参考 Scalars 文档 与 Types and Fields 文档。第二步调用 createUnionType 组合成员import { createUnionType } from type-graphql; const SearchResultUnion createUnionType({ name: SearchResult, // GraphQL Union 在 schema 中的名字 types: () [Movie, Actor] as const, // 返回对象类型类元组的函数 });关键点解析types必须是一个函数thunk返回[Movie, Actor]这样的对象类型类数组。这是为了在 schema 构建时惰性求值避免循环引用问题例如类型 A 引用 B、B 又引用 A 时模块初始化顺序导致的死循环[ ] as const语法把数组字面量断言为只读元组tuple让 TypeScript 编译器能精确推断出联合成员从而让typeof SearchResultUnion收敛为Movie | Actor这样的精确类型若不加as const数组会被推断为(typeof Movie | typeof Actor)[]类型推断质量会下降createUnionType的返回类型从源码 src/decorators/unions.ts 可以看出createUnionTypeT extends readonly ClassType[](config): UnionFromClassesT返回的是一个与Movie | Actor等价的类型符号UnionFromClassesT的实现见 src/helpers/utils.ts。因此下面的写法在编译期就是类型安全的。可选的description配置项createUnionType支持description字段用于在 GraphQL schema 中为 Union 附加描述信息见 src/decorators/unions.ts 中的UnionTypeConfig类型。在 tests/functional/unions.ts 的测试中也可以看到description的用法示例。第三步在 Resolver 中返回 UnionUnion 类型定义好后把它填进Query的返回类型注解即可import { Arg, Query, Resolver } from type-graphql; Resolver() class SearchResolver { Query(returns [SearchResultUnion]) async search(Arg(phrase) phrase: string): PromiseArraytypeof SearchResultUnion { const movies await Movies.findAll(phrase); const actors await Actors.findAll(phrase); return [...movies, ...actors]; } }两个要点必须显式写返回类型注解Query(returns [SearchResultUnion])这一句不能省略。因为 TypeScript 的装饰器元数据反射emitDecoratorMetadata无法表达 Union 这种组合类型必须靠装饰器参数显式告诉 TypeGraphQL 返回类型编译期类型安全方法签名使用PromiseArraytypeof SearchResultUnion由于typeof SearchResultUnion等价于Movie | Actor编译期就能保证返回的数组元素只能是这两类之一。在 examples/enums-and-unions/resolver.ts 中有完整的可运行示例search查询同时过滤食谱数据与厨师数据返回Arraytypeof SearchResult。运行时类型解析默认 instanceof 与自定义 resolveType默认机制必须返回类型类的实例Union 是 GraphQL 的抽象类型schema 运行时必须知道“当前返回的这个值到底属于哪个成员类型”。TypeGraphQL 的默认机制是schema 生成器在构建 Union 时若未提供resolveType会使用默认函数用instanceof在types元组中逐个查找匹配的对象类型类若instanceof全部不命中会抛出UnionResolveTypeError见 src/errors/UnionResolveTypeError.ts 与 src/schema/schema-generator.ts错误信息明确提示“你需要返回对象类型类的实例而不是普通对象”。因此使用默认机制时 resolver 必须返回具体类型类的实例例如new Movie()或 ORM 实体实例不能直接返回纯 JSON 对象plain object——否则graphql-js无法识别底层 GraphQL 类型schema 执行时会报错。自定义 resolveType支持返回纯对象如果不想强制 resolver 构造类型类实例可以在createUnionType配置中提供自己的resolveType函数根据数据对象的形状shape判断类型const SearchResultUnion createUnionType({ name: SearchResult, types: () [Movie, Actor] as const, // 根据返回数据对象的形状检测其所属类型 resolveType: value { if (rating in value) { return Movie; // 返回对象类型类带 ObjectType() 的那个 } if (age in value) { return Actor; // 或者返回类型在 schema 中的名字字符串 } return undefined; }, });resolveType返回值支持两种形式见 src/typings/TypeResolver.ts 中Maybestring | ClassType的定义对象类型类如Movieschema 生成器会把它映射为对应的 GraphQL 类型名字符串如Actor直接作为 schema 中的类型名。schema-generator的getResolveTypeFunctionsrc/schema/schema-generator.ts会包装用户的resolveType若返回字符串则直接透传若返回类则查表转换为对应 GraphQL 类型名。注意若resolveType返回undefined或对任何值都无法判定GraphQL 执行时会报“Abstract type must resolve to an Object type at runtime”错误——这一点在 tests/functional/unions.ts 中有专门测试用例验证。类型判定建议使用判别字段如rating、age做in检查字段名必须与各成员类型的Field()定义一致判定顺序要覆盖全部成员末尾兜底返回undefined或抛错以便尽早暴露数据异常若成员类型共享某个唯一字段也可以优先用该字段判定避免误判。客户端查询 Union内联片段Union 成员没有公共字段客户端必须先通过内联片段inline fragment按类型取字段并且可以借助__typename字段做前端分发。对应的 GraphQL 查询如下query { search(phrase: Holmes) { ... on Actor { # Maybe Katie Holmes? name age } ... on Movie { # For sure Sherlock Holmes! name rating } } }要点... on Actor { ... }/... on Movie { ... }两种内联片段写法name字段在两个片段里都可以取因为两个类型都有name字段而age、rating只能在对应片段里取若想在前端拿到具体类型可额外请求__typename在 tests/functional/unions.ts 的测试查询中大量使用__typename断言类型解析结果。源码实现Union 元数据到 GraphQLUnionType 的转化了解底层实现有助于排查“类型解析失败”“schema 构建异常”等问题。整个链路如下收集阶段createUnionType调用getMetadataStorage().collectUnionMetadata(...)src/decorators/unions.ts把name、description、getClassTypestypes函数、resolveType存入全局元数据存储MetadataStorage.unions数组src/metadata/metadata-storage.ts并生成一个以 Union 名字命名的Symbol作为返回标识。元数据定义见 src/metadata/definitions/union-metadata.ts构建阶段SchemaGenerator.buildTypesInfosrc/schema/schema-generator.ts遍历unions元数据为每个 Union 实例化GraphQLUnionTypetypes采用闭包 惰性 thunk首次访问时再通过getClassTypes()从已构建的objectTypesInfoMap解析成员类型这天然规避了循环引用问题有resolveType时用getResolveTypeFunction包装用户的判定函数支持 async 函数、类与字符串两种返回形式无resolveType时使用默认instanceof判定函数找不到匹配就抛UnionResolveTypeError。因此只要在types数组中声明的成员都带ObjectType()schema 构建时 TypeGraphQL 就能正确生成 Union 类型而运行时的类型识别则完全取决于返回的实例类型或你提供的resolveType。实战示例enums-and-unions仓库的 examples/enums-and-unions/ 目录提供了完整可运行的联合类型 枚举示例适合对照学习search-result.union.ts定义SearchResultUnion成员为Recipe与Cookrecipe.type.tsRecipe对象类型cook.type.tsCook对象类型nameyearsOfExperience: Intdifficulty.enum.ts枚举类型定义配合 Union 使用resolver.tssearch查询返回Arraytypeof SearchResult内部将食谱与厨师结果合并返回examples.graphql可直接执行的示例查询schema.graphql构建出的 schema 文件可见union SearchResult Recipe | Cook的声明。常见问题排查现象原因解决schema 构建报错或查询报 “Cannot resolve type for union X”resolver 返回了纯对象而非类型类实例且未提供resolveType返回类型类实例或为 Union 配置resolveType执行报 “Abstract type must resolve to an Object type”自定义resolveType对当前值返回了undefined确保所有分支都有返回值必要时末尾抛错类型推断不精确typeof结果变成数组类型忘记写as const使用[A, B] as const元组断言成员类型字段名写错导致误判resolveType中的in检查字段名与Field()不一致统一使用Field()定义的 schema 字段名小结TypeGraphQL 用createUnionType把“类装饰器定义对象类型”与“GraphQL 抽象类型”无缝衔接定义时types: () [...] as const惰性组合成员并保证编译期类型安全运行时默认用instanceof判定、也可用自定义resolveType支持纯对象返回。配合内联片段查询Union 是处理“多形态返回值”API 的标准解法实际项目中可直接参考仓库的 enums-and-unions 示例并结合 测试用例 验证边界行为。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐type-graphql 联合类型Union实战指南用 createUnionType 构建灵活的多类型查询type graphql 联合类型Union实战指南用 createUnionType 构建灵活的多类型查询 导读 本文聚焦 TypeGraphQL 中后端GraphQLAPI设计type-graphql 联合类型Union实战指南用 createUnionType 定义灵活的 GraphQL 返回类型type graphql 联合类型Union实战指南用 createUnionType 定义灵活的 GraphQL 返回类型 联合类型Union是 G后端GraphQLAPI设计bottom 的部署流程全解析Nightly 与 Stable 双轨发布、crates.io / Chocolatey / winget 手动分发实操bottom 的部署流程全解析Nightly 与 Stable 双轨发布、crates.io / Chocolatey / winget 手动分发实操 本文面后端GraphQLAPI设计上一篇mp-html组件在鸿蒙NEXT平台上的适配挑战与解决方案下一篇FUXA项目中的Token认证机制解析与访客模式实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考