定位
Fastify 的核心是用 JSON Schema 定义请求和响应,换取两个好处:请求校验自动完成,序列化速度更快(它用 schema 编译出序列化函数,比 JSON.stringify 快得多)。
它适合「对吞吐量有要求、愿意多写点声明」的团队。
安装
npm init -y
npm install fastify
最小示例
const fastify = require('fastify')({ logger: true })
// 基础路由
fastify.get('/', async () => ({ hello: 'world' }))
// 带 schema 的路由
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 2 },
email: { type: 'string', format: 'email' },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
},
}, async (req, reply) => {
// req.body 已经被校验过,类型也对
const user = await createUser(req.body)
reply.code(201).send(user)
})
fastify.listen({ port: 3000 })
请求体不合法会自动返回 400,错误格式规范,不需要写校验代码。
核心概念
1. Schema 驱动
Schema 有三个作用,不只是校验:
| 作用 | 说明 |
|---|---|
| 请求校验 | 自动返回 400,带字段级错误信息 |
| 序列化加速 | 编译出专用的序列化函数,比 JSON.stringify 快 2-3 倍 |
| 文档生成 | 配合 @fastify/swagger 自动产出 OpenAPI |
响应 schema 还有一个副作用:它会过滤掉 schema 里没声明的字段。这是防止敏感字段泄漏的好办法,但也会让「我明明返回了这个字段」变成玄学问题。
2. 插件系统
Fastify 的一切都是插件,通过 register 加载:
fastify.register(require('@fastify/cors'))
fastify.register(require('@fastify/jwt'), { secret: 'xxx' })
// 自定义插件
fastify.register(async function (instance, opts) {
instance.get('/plugin-route', async () => ({ ok: true }))
}, { prefix: '/api' })
3. 封装作用域
这是最容易踩的坑:插件里的装饰器和钩子默认只在该插件作用域内可见。
fastify.register(async (instance) => {
instance.decorate('db', db) // 只在这个插件内可见
instance.get('/a', async () => instance.db) // ✅
})
fastify.get('/b', async () => fastify.db) // ❌ undefined
要让装饰器全局可见,用 fastify-plugin 包一层:
const fp = require('fastify-plugin')
fastify.register(fp(async (instance) => {
instance.decorate('db', db) // 现在全局可见
}))
常见坑
① 响应 schema 会吞字段
schema: { response: { 200: { properties: { id: {type:'integer'} } } } }
// handler 返回 { id: 1, secret: 'xxx' } → 响应里只有 id
看起来是 bug,其实是特性。调试时先怀疑 schema。
② 钩子的执行顺序
onRequest → preParsing → preValidation → preHandler → handler → preSerialization → onSend → onResponse。
鉴权放 onRequest 或 preHandler;想拿到解析后的 body 要放 preValidation 之后。
③ 异步钩子必须 await
fastify.addHook('preHandler', async (req, reply) => {
await checkAuth(req) // 不 await 的话鉴权会失效,但请求照常继续
})
什么时候选它
- API 服务,有明确的数据契约
- 对吞吐量有要求(比 Express 快 2-3 倍)
- 需要自动生成 OpenAPI 文档
- 愿意为性能多写 schema
不选它的场景:快速原型(写 schema 有成本)、需要海量 Express 中间件、团队不接受声明式风格。
一个性价比判断
写 schema 的成本,值不值?
- 如果你的接口是给前端/第三方用的,值:schema 同时解决了校验、文档、序列化三个问题
- 如果是内部临时接口,不值:直接用 Express 更快
Fastify 的性能优势在「响应体大、QPS 高」的场景才明显。小接口上,JSON.stringify 和编译序列化的差距用户感知不到。