40岁了,最近再学bun和ai。
上周压测,QPS 一上 1000,CPU 飙到 80%。
查了半天,瓶颈在校验上。
订单提交接口,30 来个字段。
每个字段链式校验 5、6 层。
QPS 一千,每秒 15 万次方法调用。
V8 看着都替它累。
调成 TypeBox + Ajv,编译一次,CPU 直接腰斩。
这就是今天要聊的:解释器 vs 编译器。
Zod 是 TS 圈的校验 老炮,写法爽生态大。TypeBox 是 JSON Schema 派的新秀,速度是 Zod 的 10 到 15 倍。一个解释器 跑、一个编译器编,速度能一样吗。
bash
// TypeBox + Ajv:先把 Schema 编译成函数,后面就一泻千里
import { Type, type Static } from '@sinclair/typebox'
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const UserSchema = Type.Object({
id: Type.String({ format: 'uuid' }),
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
})
type User = Static<typeof UserSchema>
const ajv = addFormats(new Ajv({ allErrors: true }))
const validate = ajv.compile(UserSchema) // 编译一次,吃一辈子
const ok = validate({ id: 'xxx', name: '老王', email: 'a@b.com', age: 30 })
console.log(ok) // true
看着不起眼?
ajv.compile 这一步,才是 TypeBox 快得邪乎的关键。
Zod 是边校验边判断类型,
TypeBox 是先把校验逻辑编译成原生 JS 函数,再去跑数据。
一个解释器,一个编译器。
速度能一样吗?
一句话先看明白这俩玩意儿
Zod 是一套"链式 API 校验库"。TypeBox 是一套"JSON Schema 构建器",顺便送你 TS 类型。
差别在哪?Zod 的 schema 是个自家 ZodObject 实例,得自己跑。TypeBox 的 schema 是个标准的 JSON Schema,能直接喂给 Ajv、Fastify、OpenAPI。
Static<typeof Schema> 一拉,TS 类型也出来了。一鱼三吃:运行时校验、编译时类型、OpenAPI 文档。
一、Zod 慢在哪?老铁先看看这段
Zod 的写法大家都熟:
bash
import { z } from 'zod'
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(0).max(150),
})
const result = UserSchema.safeParse({ id: 'xxx', name: '老王', email: 'a@b.com', age: 30 })
if (result.success) console.log(result.data)
看着是挺爽。z.string().email().min(8),链子一拉完事。但每跑一次校验,它都得逐个判断:
-
• 这是
ZodString实例吗? -
• 调
.email()内部方法。 -
• 调
.min()内部方法。 -
• 再调
.uuid()内部方法。
每一个方法都是一次函数调用。100 个字段 100 次方法调用。V8 看着都替你累。
二、TypeBox 是怎么把 Zod 按在地上摩擦的
TypeBox 不做这些虚的。它只负责把 schema 写出来,然后交给 Ajv。
bash
import { Type, type Static } from '@sinclair/typebox'
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const UserSchema = Type.Object({
id: Type.String({ format: 'uuid' }),
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
})
type User = Static<typeof UserSchema>
const ajv = addFormats(new Ajv({ allErrors: true }))
const validate = ajv.compile(UserSchema)
const data: User = { id: 'xxx', name: '老王', email: 'a@b.com', age: 30 }
console.log(validate(data)) // true
ajv.compile 干了啥?它把上面的 schema,编译成一段优化过的 JS 函数。简单说,就是这么个东西:
bash
// 编译产物长这样(简化版)
function validate(data) {
if (typeof data !== 'object') return false
if (typeof data.id !== 'string') return false
if (!/^[0-9a-f-]{36}$/.test(data.id)) return false
if (typeof data.name !== 'string') return false
if (data.name.length < 1) return false
if (!/^.+@.+\..+$/.test(data.email)) return false
if (typeof data.age !== 'number') return false
if (data.age < 0 || data.age > 150) return false
return true
}
全是 typeof、正则、布尔判断。V8 最爱的形状。循环跑起来,CPU 几乎不费力。
三、benchmark 数据,不是嘴炮
我自己跑过,也参考了 sinclair 的官方数据。
100 万次复杂对象校验:
| 库 | 耗时 | 相对速度 | | :-- | :-- | :-- | | TypeBox + Ajv | ~80ms | 1x 基准 | | TypeBox (内置 Compiler) | ~260ms | 慢 3 倍 | | Zod v4 (parse) | ~880ms | 慢 11 倍 | | Zod v3 (parse) | ~1400ms | 慢 17 倍 |
zod v4 比 v3 已经快了不少。但跟 TypeBox + Ajv 比,还是被按在地上摩擦。v3 用户更惨,慢 17 倍。
简单字符串校验的差距更夸张:
bash
z.string().email().parse(x) 132 µs
TypeBox + Ajv.compile + 校验 ~10 µs
快 10 倍以上。越简单的校验,TypeBox 优势越明显。
四、再看个真实业务场景
来个电商订单校验。包含嵌套对象、数组、枚举、可选项。
bash
import { Type, type Static } from '@sinclair/typebox'
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const OrderSchema = Type.Object({
orderId: Type.String({ format: 'uuid' }),
userId: Type.String(),
items: Type.Array(
Type.Object({
sku: Type.String(),
qty: Type.Integer({ minimum: 1, maximum: 999 }),
price: Type.Number({ minimum: 0 }),
}),
{ minItems: 1, maxItems: 100 }
),
address: Type.Object({
province: Type.String(),
city: Type.String(),
detail: Type.String({ minLength: 5 }),
}),
payMethod: Type.Union([
Type.Literal('alipay'),
Type.Literal('wechat'),
Type.Literal('card'),
]),
remark: Type.Optional(Type.String({ maxLength: 200 })),
})
type Order = Static<typeof OrderSchema>
const ajv = addFormats(new Ajv({ allErrors: true, useDefaults: true }))
const validateOrder = ajv.compile(OrderSchema)
const order: Order = {
orderId: 'xxx',
userId: 'u001',
items: [{ sku: 'A001', qty: 2, price: 99.9 }],
address: { province: '辽宁', city: '沈阳', detail: '和平区南京北街 100 号' },
payMethod: 'wechat',
}
if (validateOrder(order)) {
console.log('订单格式没问题')
} else {
console.log(validateOrder.errors)
}
Zod 写法能短一丢丢,但校验时挨个调方法。TypeBox 写法稍长,编译完就是铁甲。接口越复杂、数据越大,差距越明显。
五、TypeBox 的 JSON Schema 是真家伙
这一条是 TypeBox 真正的杀手锏。
TypeBox 写出来的 schema,就是标准 JSON Schema。所以它能直接对接:
-
• Fastify:原生支持 JSON Schema 校验和响应序列化
-
• OpenAPI:直接生成 API 文档
-
• AI 工具调用:OpenAI、Anthropic、Gemini 全都吃 JSON Schema
-
• Ajv:业界最快的 JSON Schema 校验器
-
• drizzle-typebox:数据库 schema 转 TypeBox
拿 AI function calling 举个例子:
bash
import { Type, type Static } from '@sinclair/typebox'
// 给 AI 用的工具定义
const WeatherTool = {
name: 'get_weather',
description: '查询某城市天气',
parameters: Type.Object({
city: Type.String({ description: '城市名,比如沈阳' }),
unit: Type.Union([Type.Literal('c'), Type.Literal('f')], {
default: 'c',
}),
}),
}
type WeatherArgs = Static<typeof WeatherTool.parameters>
// 直接发给大模型
console.log(JSON.stringify(WeatherTool.parameters))
// 输出标准 JSON Schema,OpenAI / Claude / Gemini 都能用
Zod 想做这事?得引入 zod-to-json-schema 包。转出来的还时不时有兼容问题。
TypeBox 一步到位。写一次,三端通用:TS 类型、运行时校验、AI 工具定义。
六、Elysia 默认就集成了 TypeBox
跑过 Elysia 的老铁都知道:那个 21 倍的 Express 性能碾压,靠的就是 TypeBox 加 Ajv 那一套。
Elysia 不用你装,不用你配。装上 elysia 包的那一刻,TypeBox 就已经在里面了 。它直接 fork 了 TypeBox,改了个名叫 Elysia.t:
bash
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/user', ({ body }) => `Hello ${body.name}`, {
body: t.Object({
name: t.String({ minLength: 1 }),
age: t.Integer({ minimum: 0 }),
email: t.String({ format: 'email' }),
}),
})
.listen(3000)
body 后面那个 t.Object,就是 TypeBox 原生写法。零成本接入,连 import 都不用改。
Elysia 顺手把 schema 喂给 Ajv,编译成函数缓存起来。请求一来,直接跑函数,零解释开销。
这就是 Elysia 比 Express 快 21 倍的真相之一。不是单点优化,是每一个环节都把解释器换成编译器。
七、TypeBox 还能这么玩
你以为 TypeBox 就只能校验?太小看它了。
1. 数据库 schema 一把梭
bash
import { Type, type Static } from '@sinclair/typebox'
const UserModel = Type.Object({
id: Type.String(),
email: Type.String({ format: 'email' }),
createdAt: Type.String({ format: 'date-time' }),
})
type User = Static<typeof UserModel>
// 同时给 TS 类型、API 校验、OpenAPI 文档用
// 一份代码,三处生效
2. 条件类型也支持
bash
import { Type, type Static } from '@sinclair/typebox'
const PaymentSchema = Type.Object({
method: Type.Union([Type.Literal('card'), Type.Literal('crypto')]),
cardNo: Type.Optional(Type.String()),
wallet: Type.Optional(Type.String()),
})
// 配合 Type.Union + discriminator,TS 类型能精确推断
// card 模式下 wallet 不该有值,TS 会提示
3. 配合 tRPC 玩转 RPC
虽然 tRPC 默认用 Zod。但你想换 TypeBox 也能用,schema 写一遍,前后端通吃。
八、那 Zod 还能用吗?能!
说了这么多 TypeBox 的好。但 Zod 也不是没优势。
Zod 优势:
-
• 链式 API 写起来真的爽,可读性强
-
• 错误信息开箱即用,体验好
-
• 生态最大,tRPC、React Hook Form、Drizzle 默认支持
-
• 新人上手快,半小时能干活
TypeBox 优势:
-
• 速度是 Zod 的 10 到 15 倍
-
• schema 直接是 JSON Schema,三端通用
-
• TypeScript 类型推断更精确
-
• Elysia、Fastify 原生支持
老铁选型建议:
-
• 写普通业务、CRUD,Zod 够用
-
• 写高 QPS 接口、AI 工具、大数据校验,换 TypeBox
-
• 用 Elysia 写 Bun 项目,闭眼选 TypeBox
-
• 用 Fastify 写 Node 项目,TypeBox 完胜
九、上手 TypeBox,三步搞定
第一步:装包。
bash
bun add @sinclair/typebox ajv ajv-formats
第二步:写 schema。
bash
import { Type, type Static } from '@sinclair/typebox'
const TodoSchema = Type.Object({
id: Type.String(),
title: Type.String({ minLength: 1, maxLength: 200 }),
done: Type.Boolean({ default: false }),
tag: Type.Optional(Type.String()),
})
type Todo = Static<typeof TodoSchema>
第三步:编译 + 校验。
bash
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const ajv = addFormats(new Ajv({ allErrors: true }))
const validate = ajv.compile(TodoSchema)
const todo = { id: '1', title: '学 TypeBox', done: false }
console.log(validate(todo)) // true
完事。40 岁老哥亲自试过,从 Zod 切到 TypeBox,半小时搞定。后面接口压力小了一半,CPU 占用直接降 30%。
写在最后
TypeBox 不是要干掉 Zod。它俩是不同赛道的工具。
Zod 走"开发者体验"路线。TypeBox 走"性能 + 标准"路线。
你的项目卡在性能上?你对接的是 Fastify、Elysia?你在做 AI 工具调用?
别犹豫,TypeBox + Ajv 安排上。10 到 15 倍的提升,真不是吹出来的。是 Ajv 编译器一行一行编出来的。
老铁,代码写起来吧。别光看,跑一遍才是真的。
参考资料:
-
• TypeBox GitHub
-
• Ajv 官方文档
-
• Elysia TypeBox 模式
-
• Elysia ACM 论文
往期文章