跳过主要内容
Capgo logo
开发 移动

OpenAPI TypeScript: 生成类型、客户端和验证

了解OpenAPI TypeScript生成的工作原理,从头到尾。生成类型、编写客户端、在运行时验证并从CI安全地部署。

OpenAPI TypeScript: 生成类型、客户端和验证

你通常可以在API pipeline开始撒谎的那一刻发现。一个schema发生了变化,生成的类型更新了,没有抱怨,PR通过了,然后前端的人继续读取旧的响应形状,因为包装器将错误丢弃掉了。 这就是OpenAPI TypeScript的问题,而不是一个生成器是否能输出接口的问题。

有用的问题更难。您想要在schema、传输和验证之间的哪种契约?哪些部分应该在构建时间失败,而不是在运行时泄露? 一旦您将OpenAPI TypeScript框定为一个pipeline选择,trade-offs就会变得更加清晰,工具就不会假装是整个解决方案。 OpenAPI TypeScript 目录

为什么生成的类型不是安全的__CAPGO_KEEP_0__

为什么生成的类型与安全的 API 不一样

一位同事合并了一个 PR,添加了一个可选的响应字段。生成的文件更新干净,diff 看起来很乏味,大家都继续前进。然后前端继续读取一个旧的形状,通过手写的包装器进行“暂时”的 cast as any,生产环境开始像合同从未改变一样运作。

这就是生成类型的陷阱。 TypeScript 只能保护使用生成类型的 code, 仅当传输层不再擦除契约时才会这样。 OpenAPI 端给你一个模式,而不是保证每个调用者都会尊重它。关于理解 __CAPGO_KEEP_0__ 连接的 关于理解API连接的讨论 哪里失败了

最常见的断点是乏味的,而不是奇特的。

模式漂移 发生在 OpenAPI spec 和已部署的服务不再匹配时。 部分覆盖 在 spec 只模拟快乐路径时出现,应用程序依赖于未文档化的边缘案例。 手写包装器 经常是类型被弱化的地方,尤其是当有人想“快速行动”并使用 __CAPGO_KEEP_0__ any 或是一个松散的响应 cast。

实践规则: 如果包装器可以撒谎,生成器就无法拯救你。

There’s also a runtime gap. TypeScript types disappear after compilation, so they can’t reject malformed JSON coming over the wire. The network does not care what your editor inferred, and that’s why a generated client is only one layer in a safer API pipeline.

更广泛的运营问题是安全性和合同纪律,而不是仅仅是开发人员的便利。如果您想了解 API 合同如何在更大的应用程序生命周期中放置,这个内部指南 关于 API 应用商店合规性安全标准 是一个有用的伴侣。

成熟的方式来思考 typescript.openapi 是这个。它给您一个严格的 schema-to-types 桥接,这很好,但它并不验证请求、强制运行时负载形状或阻止一个懒散的包装器破坏一切。生成器是容易的 20%。剩下的就是管道设计,而这就是团队要么获得信任,要么积累错误信任的地方。

从 OpenAPI Spec 生成 TypeScript 类型

截图来自 https://openapi-ts.dev

最轻便的有用设置通常是能在真实的仓库中存活下来的一个。将 OpenAPI spec 保持在同一个仓库中,生成一个已提交的类型文件,并在 CI 中使漂移可见,而不是依赖于某人记住刷新步骤。一个命令如 npx openapi-typescript ./openapi.yaml -o ./src/types/api.d.ts 给你一个可预测的输出文件,审阅者可以像检查任何其他源代码变化一样检查它。

实际上重要的标志

标志 -o 输出标志很重要,因为它使生成的艺术品明确。 --immutable 当你希望生成的类型保留 readonly 意图在输出中,并且 --alphabetize 当模式顺序改变而没有语义意义时,保持差异稳定。 --enum 当你的团队更喜欢在生成的表面上使用枚举而不是联合时

项目自己的文档对范围有明确的说明,它是一个 类型生成器而不是客户端运行时或请求层,且这种限制有助于你想要轻量级、类型优先的设置。它的仓库也展示了工具的维护模型,这是为什么开源工具在生产环境中能持久的原因之一,正如在 开源维护的案例. 一种实用的读物是该项目的 GitHub 仓库和 CLI 文档.

Wire 生成 package.json 因此,命令位于构建脚本旁边,然后在规范发生变化时运行它。在 CI 中,重新生成文件并在发生漂移时失败。这样可以将合同更改转换为可见的审查工作,而不是静默的运行时风险。 git diff schema 方面与命令行一样重要。该项目建议

所以 compilerOptions.noUncheckedIndexedAccess 变成 additionalProperties become T | undefined而不是与额外的组合混合使用,并且在 placement 不明确时将 oneOf 放在根目录下,因为错误的定义可能会从生成的输出中消失。最后一个细节可以节省时间 $defs 永远不会产生 openapi-typescript OpenAPI TypeScript any因此,缺失的schema细节会尽早暴露出来,而不是隐藏在宽松类型下。

要么保持规范明确,要么生成器会忠实地将模糊性暴露回给你。

那条持续的工作流程很简单。将规范放在版本控制下,生成器在构建时重新生成,提交生成的文件,让类型检查器在任何人合并不匹配的内容之前抱怨。这样就给了你整个管道的稳定契约边界。

选择纯类型、全客户端和无代码生成之间的最佳选择

模式 构建时间 输出文件 包体积 最佳匹配
纯类型加薄包装 快速 少 低 希望拥有控制权并且运行时接口较少的团队
全客户端代码生成 较慢 很多 较高 希望快速交付并且自动生成操作的团队
无代码生成请求构建器 快速 无或最小 低 单代码库应用,偏好手写传输逻辑

哪种工具获胜并不是问题。关键是哪种 pipeline 形式适合你的仓库、团队以及API中发生的变更量。在2025年的一项大型OpenAPI规范benchmark中,约75,000行,2MB,约1,200个操作 生成的输出约为1.5秒, openapi-typescript 相比之下,其他工具的平均时间约为8.0秒 5.5秒 18.1秒 8.0 秒 for @hey-api/openapi-ts, 5.5 秒 for Orval, and 18.1 秒 for Kubb,同时也能生成一个单独的输出文件 16 为了 hey-api, 2,719 为了 Orval,并且 3,877 为了 Kubb (benchmark 详情).

纯类型优先控制

纯类型的设置与手写请求层配对得很好,因为您可以保持运行时小且 API 表面平淡。这在捆绑敏感的前端和拥有规范和消费者团队的应用中很重要。如果您需要一个提醒,开发者体验不仅仅是语法糖,那么 开发者体验角度 更容易判断您的客户端 code 短、明显且可审查。

全客户端优先手头速度

openapi-generator, hey-api, Orval,并且 Kubb 所有尝试做更多的事情。这样可以在你想要请求方法、模型和管道生成时很有帮助,尤其是在后端和前端团队之间的大规模交接时。成本在上面的benchmark中很明显,更多的生成文件、更多的运行时表面和更多的构建摩擦空间随着规范的增长而增长。

没有代码生成器偏爱本地重构

类型化的请求生成器和 fetch 包装器在一个代码库拥有两端形状并且API变化紧密协调时很好用。缺点是维护纪律。越多的团队和仓库之间的生产者和消费者,越不可能手写的请求层保持不变,除非你强制执行合同测试。

核心决策点不是意识形态。如果你的打包预算很紧张,纯类型很有吸引力。如果你的团队想要最大限度的支架并且可以承受输出,完整的客户端可以减少设置时间。如果你想要最少的移动部分并且可以保持合同接近,无代码生成请求生成器可以是正确的内部交易。

在Fetch或Axios周围编写一个薄的类型化客户端

一个图表,说明使用生成的TypeScriptAPI定义为Web请求的类型化客户端包装器过程。

薄的包装器是生成器停止的地方,应用code开始的地方。包装器应该暴露一个函数每个操作,接受类型化的参数和查询对象,并将调用转发给 fetch 或一个注入的 axios 实例,而不试图聪明。 在大多数生产环境中,这层会保持在 30–60 行 因为生成的类型已经包含了大部分的形状。

这里的思维模型仍然有效:

  • 路径参数保持类型化 所以 /users/{id} 不能在没有 id.
  • 查询对象保持类型化 所以可选过滤器不会变成字符串汤。
  • 响应体保持类型化 所以解析code可以信任它期望的窄形状。

像这样的包装器故意地很无聊。它不应该在其他地方属于的地方发明重试、转换或身份验证策略。如果这些属于其他地方,它应该将类型化的请求从运输层移动到,然后将类型化的结果返回上层。

保持包装器无聊且依赖轻量,否则每次代码生成变化都会波及你的应用。

常见的失败是通过修补不匹配来 as any 当生成的类型与旧的包装器签名不符时。 这会为您带来绿色的构建和脆弱的应用。 它还隐藏了您希望生成器揭示的合同破坏。

对于喜欢 Axios 的团队,模式是相同的,只是传输实现发生了变化。 对于希望更简单的浏览器端 code fetch 通常足够。 重要的是请求函数接受生成的路径类型并返回类型化的响应,而不是稍后进行大量操作的松散对象。

如果您使用这个接口良好, OpenAPI TypeScript 给您一个清晰的劳动力分配。 schema 存在于规范中,传输存在于包装器中,应用看到类型化的操作而不是 ad hoc 请求 code。

添加运行时验证 zod、ajv 或 io-ts

TypeScript 类型在运行时消失,网络不关心编辑器的信心。 这就是安全模式不是“生成类型并希望”,而是“生成类型,然后在边界处进行验证”,在那里不受信任的数据进入应用。 生成的 schema 仍然是真实的,验证库如 zod, ajv,和 io-ts 处理编译时类型无法编译的边界检查。

在数据进入时验证

对于 React 应用,边界通常在请求解析后,payload 进入状态之前。 对于服务器,边界是在 payload 写入数据库或交付给业务规则之前。 规则很简单,保持验证靠近边界,避免在特性 code 中散布手动检查。

A zod 可以反映生成响应形状而不替换它的形状:

import { z } from "zod";

const WeatherForecastSchema = z.object({
  date: z.string(),
  temperatureC: z.number(),
  summary: z.string().nullable(),
  temperatureF: z.number().optional(),
});

该示例验证了schema标记为可选的字段,并且它保持了与生成器产生的运行时检查一致。 ajv 这是一个当您希望在服务器上实现高吞吐量JSON Schema验证时的强大选择, io-ts 而仍然适合已经生活在 fp-ts 的团队。

大错误是验证太晚。如果负载先进入您的应用程序,类型系统已经被绕过,错误有地方躲藏。关于 的简短指南 与这个思维方式配对得很好,因为两者都在捕获坏假设的早期阶段时,单元测试和边界验证都表现最佳。

清晰的层次结构是可预测的。 OpenAPI TypeScript 生成合同,验证器检查运行时负载,而您的应用程序code只看到经过了这两步的数据。这样做比依赖静态类型来监管不受信任的响应要好得多。

将生成、验证和合同测试放在CI中

截图来自https://github.com

一个持续的pipeline将合同转化为门槛,而不是建议。重新生成类型,失败于漂移,运行 tsc --noEmit,并在合并前使用模拟或合同工具检查API形状。如果您将生成器版本固定在 package.json,那么两个工程师就无法意外地从同一个规范中产生不同的输出。

一个简单的GitHub Actions形状

一个实际的工作流程如下:

  1. 从仓库或生成的源代码中拉取规范。
  2. 重新生成类型。
  3. 如果 git diff 显示有变化。
  4. Run tsc --noEmit.
  5. 执行一个合同测试,针对一个模拟服务器,如Prism或Spectral背后的检查。

合同测试和快照测试之间的关键区别在于范围。快照通常告诉你文件发生了变化。合同测试则告诉你形状是否仍然像规范中所说的那样行为。

A mock server is especially useful when backend and frontend work are separated by time or team boundaries. It gives consumer code a predictable API surface while still checking the actual contract rather than a hard-coded fixture. The 持续集成设置指南 如果您的团队仍然需要一个干净、可重复的CI基线,则是有用的参考。

固定生成器版本避免了代码生成管道中最糟糕的失败之一:不可见的输出偏差。如果一个开发者在本地升级了生成器,而另一个没有,那么生成的文件就可以成为随机噪声而不是信号的来源。CI应该使这变得不可能。

结果是一个管道,其中schema变化、类型生成、编译器检查和合同测试都相互强化。这就是使工作流诚实的原因。

可维护的管道、性能和最后的检查清单

一个清单,展示了四个关键步骤,用于维护OpenAPI TypeScript管道以支持软件开发项目。

只有那些有着乏味治理的管道才能存活下来。将规范版本化,像code一样审查schema变化,固定生成器,并记录破坏性变化如何获得批准。如果流程模糊不清,人们会绕过它,然后生成的类型就变成了装饰品而不是强制执行。

几个性能调节器实际上是有意义的

增量生成在单个仓库中有所帮助,其中规范变化频繁,但只有一个包消费它。 tsc --incremental 可以减少重复的编译工作,并且在生产构建中禁用不需要的输出标志可以使生成的表面更小。在实践中,最大收益仍然是社会性的,而不是技术性的,因为可预测的管道比聪明的管道更频繁地运行。

下面的检查清单是值得保留的:

  • 版本固定: 将版本固定在 openapi-typescript 以便输出不会在机器之间漂移。 package.json schema审查:
  • 将规范变化视为可审查的合同变化,而不是日常工作。 漂移检测:
  • Lock the CI 中重新生成并在 diff 时失败。
  • 边缘验证: 在应用状态或持久性之前解析未受信任的负载。
  • 契约测试: 运行一个 mock 后台检查,证明消费者 code仍然符合 schema。
  • 破坏性更改政策: 记录谁批准形状更改以及如何通知客户。

包含这些门控的管道不仅生成类型,还使契约可见。这种可见性是保持团队不信任仅看起来安全的文件的关键。

如果您正在部署 Capacitor 或 Electron 应用,并希望您的更新管道表现出同样的纪律,Capgo为您提供了一种实用的方法来快速移动 JavaScript、CSS、复制、配置和资产修复,而无需等待应用商店审查。访问 Capgo 查看如何将其数字签名包、回滚保护和发布控制整合到需要速度但不失控的发布流程。

通过 Capacitor 为 Capacitor 应用实时更新

当 web 层的 bug 活跃时,通过 Capgo 将修复推送到用户,而不是等待几天的 app store 审批。用户在后台接收更新,而原生更改仍在正常的审批路径中。

来自 Martin 的人性化支持

立即开始

最新博客文章

Capgo 为您提供了创建真正专业的移动应用所需的最佳见解。