如果你用Node.js写过API,大概率经历过这种痛苦:TypeScript类型说一套,运行时校验说另一套,OpenAPI文档又悄悄跟两边都不一致。有人给接口加了个字段,schema却没人更新;文档一直过期,直到客户端报bug才被发现。没有编译错误,也没有失败的测试——三份"事实来源"就这么各走各路。

这个问题的根源在于,大多数TypeScript API最终维护着同一份数据的三种独立描述:请求到达时执行的运行时校验编译器理解的TypeScript类型、以及展示给消费者的API文档。它们各自存在于不同文件,按不同节奏更新,彼此之间谁也看不见谁。手写的interface在运行时消失,Joi或Yup的schema能校验数据却不会免费给你类型,OpenAPI文件通常靠手工编辑——如果还有人编辑的话。

打开网易新闻 查看精彩图片

解决方案不是"再小心一点",而是用一个定义同时产出三样东西。这正是Hono和Zod通过@hono/zod-openapi组合起来能实现的效果。

Hono是什么?

Hono是一个基于Web标准API构建的小型快速Web框架,用的是Node.js、Deno、Bun和Cloudflare Workers里都有的Request和Response原生原语。和Express相比,差异很实在:Hono核心只有约14kb,在Node上处理相同负载大约比Express快5到7倍。在Bun或Cloudflare Workers上差距更大,因为这些运行时本身就对Web标准做了优化。

对大多数CRUD API来说,数据库仍然是瓶颈。但在高并发场景,或者冷启动至关重要的边缘运行时上,框架差距就是真实存在的。不过对本教程更关键的是:Hono的OpenAPI集成让你的路由定义本身就能成为单一事实来源。

一个Schema,三份产出

核心思路是定义一个Zod schema,让它同时处理运行时校验、TypeScript类型和OpenAPI文档三件事。这样校验、类型和文档从设计上就保持同步,而不是靠人为纪律去维护。

具体做法包括:

  • 一个Zod schema同时生成运行时校验、TypeScript类型和OpenAPI文档
  • 路由契约和处理逻辑分离,结构清晰
  • 数据库schema和HTTP schema作为两个有意识的分层,不混为一谈
  • 所有失败路径返回一致的错误格式
  • 把同样的模式从小型Tasks API扩展到真实的多服务系统

生产环境验证过的模式

这套模式不是纸上谈兵。作者在ClipForge——一个开源的视频处理工具包——中实际使用了这些模式,并维护至今。它们帮助校验、类型和文档在设计层面保持同步,而不是靠"记得去更新"来维持。

如果你已经了解JavaScript和基础TypeScript,知道REST API的基本概念(路由、请求体、状态码),会一点Node.js(安装包、运行脚本),就可以跟着上手。不需要提前掌握Hono、Zod或Drizzle的经验。

接下来要做的,就是把这个"三份文档漂移"的问题,压缩成一个定义。从一个小型Tasks API开始,把模式跑通,再扩展到真实的多服务系统。