跳过主要内容
开发 移动

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

学习OpenAPI TypeScript生成的整个流程。生成类型、编写客户端、在运行时验证并从CI安全地部署。

马丁·多纳迪厄

马丁·多纳迪厄

内容营销

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

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

有用的问题更难。您想要的schema、传输和验证之间的哪个契约,以及哪些部分应该在构建时间快速失败,而不是在运行时泄露?一旦您将 OpenAPI TypeScript 作为管道选择,权衡利弊变得更加清晰,工具停止假装是整个解决方案。

目录

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

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

这是生成类型的陷阱。 TypeScript只能保护消费生成类型的code,并且只有当传输层不再擦除合同时才会如此。OpenAPI侧给你一个schema,而不是保证每个调用者都尊严它。关于理解code连接的讨论是关于理解__CAPGO_KEEP_0__连接的讨论 TypeScript只能保护消费生成类型的API,并且只有当传输层不再擦除合同时才会如此。OpenAPI侧给你一个schema,而不是保证每个调用者都尊严它。关于理解API连接的讨论 因为它推动了对系统连接的讨论,而不是单一工具的讨论,所以它在这里很有用。

失败的掩护处

最常见的断点是乏味的,而不是神秘的。 模式漂移 模式漂移发生在OpenAPI规范和部署的服务不再匹配时。 部分覆盖 部分覆盖会出现于规范只模型了happy path,而应用程序依赖于未文档化的边缘案例时。 手写的包装器 通常是类型被弱化的地方,尤其是当有人想要“快速行动”并使用 any 或松散响应 cast 时。

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

有一个运行时的差距。类型脚本类型在编译后会消失,所以它们无法拒绝来自网络的错误的 JSON。网络并不关心您的编辑器推断的内容,而这是为什么生成的客户端只是一层更安全的API pipeline。

更广泛的运营问题是安全性和合同纪律,而不是仅仅是开发者方便。要了解API合同如何在更大的应用程序生命周期中如何构成,这个内部指南 API应用商店安全标准 __CAPGO_KEEP_0__

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

从OpenAPI Spec生成类型脚本类型

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

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

实际上很重要的标志

The -o 输出标志很重要,因为它使生成的工件明确。 --immutable 有用时,您希望生成的类型保留输出中的 readonly 意图,并且当模式顺序发生变化而没有语义意义时,保持 diff 稳定。 --alphabetize 当您的团队更喜欢在生成的表面上使用枚举而不是联合时,很重要。 --enum 该项目的文档很清楚,它是一个

类型生成器 ,而不是客户端运行时或请求层,且该限制有助于您想要轻量级、类型优先的设置。它的存储库还显示了工具背后的维护模型,这是为什么开源工具可以在生产中保持有效的原因之一,因为讨论在开源维护的案例 中。关于这种姿态的实用读物是该项目的__CAPGO_KEEP_0__ GitHub repository and CLI documentation.

将Wire生成到 package.json 所以命令位于构建脚本旁边,随着规范的变化而运行。在CI中,重新生成文件并在发生漂移时失败。 git diff 漂移的显示会让合同变更转化为可见的审查工作,而不是静默的运行风险。

schema侧面同样重要,命令行。项目建议 compilerOptions.noUncheckedIndexedAccess 成为 additionalProperties ,这强制了在调用位置上更安全的索引。它还建议使用 T | undefined而不是混合使用额外的组合,并且在位置不明确时将 oneOf 放在根目录下,因为错误的定义可能会从生成的输出中消失。另外一个细节会节省时间 $defs 永远不会产生 openapi-typescript ,所以缺失的schema细节会提前暴露,而不是被宽容类型掩盖。 any保持规范明确,或者生成器会忠实地将模糊性暴露给你。

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

在构建时重新生成文件,提交生成的文件,让类型检查器在任何人合并不匹配之前抱怨。这样就给了你整个管道的稳定合同边界。

选择纯类型、全客户端和无代码生成

模式 构建时间 输出文件 包体积 最佳匹配
原生云构建产品页面 短UI标签或导航项 原生云构建器比较匹配特征 纯类型与薄包装 快速
少量的代码生成量低的包体积小的运行时表面控制权的团队 较慢 很多 更高 希望快速交接和自动生成操作的团队
无需代码生成请求构建器 快速 无或最小 使用单一代码库的应用程序,偏好手写传输逻辑

选择不是“哪个工具获胜”。而是哪种pipeline形状适合您的仓库,团队,以及API看到的变动量。 在2025年的一项benchmark中,围绕一个大约有 75,000行,2MB,约1,200个操作, openapi-typescript 在大约 1.5 秒 平均比约 8.0 秒 对于 @hey-api/openapi-ts, 5.5 秒 对于 Orval, 并且 18.1 秒 对于 Kubb, 与此同时产生一个单独的输出文件 16 对于 hey-api, 2,719 对于 Orval,和 3,877Kubb (benchmark 详情).

纯类型优先控制

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

全客户端优先手动传递

openapi-generator, hey-api, Orval,和 Kubb 所有尝试做更多类型。这样可以在您想要请求方法、模型和管道一起生成时很有帮助,尤其是在后端和前端团队之间的大传递中。成本在benchmark上很明显,更多的生成文件、更多的运行时表面和更多的规范生长导致的构建摩擦。

无代码生成优先本地重构

类型化请求构建器和 fetch 当一个代码库同时拥有这两端的形状,并且API变化紧密协调时,wrapper就能发挥作用。然而,这也意味着需要维护的约束。随着团队和仓库数量的增加,生产者和消费者之间的距离越来越远,手写的请求层会越来越容易脱离同步,除非你通过强制执行契约测试来加以约束。

核心决策点并非是意识形态问题。如果你的包大小预算很紧张,那么纯类型可能会很有吸引力。如果你的团队想要最大限度地减少设置时间,并且能够承受输出,那么全客户端可能会是最佳选择。如果你想要最小化移动部分,并且能够保持契约接近,那么无代码生成的请求构建器可能会是最合适的内部权衡。

使用 Fetch 或 Axios 构建 Thin Typed Client

使用生成的 TypeScript API 定义为 web 请求的 Typed Client Wrapper 过程的示意图。

薄层是生成器停止的地方,应用程序code开始的地方。该层应该暴露一个函数,接受类型化的参数和查询对象,并将调用转发给 fetch 或注入的 axios 实例,而不试图变得聪明。在大多数生产环境中,这层代码通常不会超过30-60行,因为生成的类型已经包含了大部分的形状。 这里的思维模型是有效的: 路径参数保持类型化

所以

  • pathParamsStayTypedSo pathParamsStayTypedSo /users/{id} 不能不带参数 id.
  • Query 对象保持类型 所以可选过滤器不会变成字符串汤。
  • 响应体保持类型 所以解析 code 可以信任它期望的窄形。

像这样的包装器是故意无聊的。它不应该在其他地方属于的地方创造重试、转换或鉴权策略。它应该将类型化的操作从运输层移动到运输层,然后将类型化的结果返回上层。

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

常见的失败是通过 as any 当生成的类型与旧包装器签名不一致时,修补这些不匹配项。这样可以获得绿色构建和脆弱的应用。它还隐藏了你希望生成器揭示的契约破坏。

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

如果你善于利用这个接口, openapi typescript 给你一个清晰的劳动力分配。 Schema lives 在 spec 中,transport lives 在 wrapper 中,和 app sees typed operations 而不是 ad hoc request code。

Adding Runtime Validation with zod, ajv, or io-ts

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

Validate where the data enters

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

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

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 组合风格的团队。

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

清晰的层次结构是可预测的。 OpenAPI TypeScript generates the contract, the validator checks the runtime payload, and your app code only sees data that survived both steps. That’s a much better boundary than trusting a static type to police an untrusted response.

将生成、验证和合同测试添加到CI中

截图来自https://github.com

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

一个简单的GitHub Actions 形式

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

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

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

一个模拟服务器尤其有用,当后端和前端工作被时间或团队边界分开时。它为消费者code提供了一个可预测的API表面,同时仍然检查实际的合同而不是一个硬编码的固定。 持续集成设置指南 如果您的团队仍然需要一个干净、可重复的CI基线,那么这是一个有用的参考。

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

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

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

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

能够存活的管道是那些具有平凡治理的管道。版本化规范,审查schema变化,如code,固定生成器,记录如何批准破坏性变化。如果过程模糊,人们会绕过它,生成的类型就变成了装饰而不是强制执行。

几个性能杠杆实际上很重要

增量生成有助于在单个包中频繁更改规范的多包项目。 tsc --incremental 可以裁剪重复的编译工作,并且在生产构建中禁用不需要的输出标志可以使生成的表面更小。在实践中,最大收益仍然是社会而不是技术,因为可预测的管道比聪明的管道更常被运行。

以下清单是值得保留的:

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

A pipeline that includes those gates doesn’t just generate types, it makes the contract visible. That visibility is what keeps teams from trusting a file that only looks safe.

如果您正在发布 Capacitor 或 Electron 应用程序,并希望您的更新管道表现出同样的纪律,Capgo 给您一个实用的方法来快速移动 JavaScript、CSS、复制、配置和资产修复,而不必等待应用商店审查。访问 Capgo 查看其签名包、回滚保护和发布控制如何适应需要速度而不失控的发布过程。

实时更新Capacitor应用

当Web层bug出现时,通过Capgo将修复推送给用户,而不是等待几天的应用商店审批。用户在后台接收更新,而原生变化仍在正常审查路径中。

来自马丁的人性化支持

立即开始

最新博客文章

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