如果你正在用 Express 构建 API 服务,却对它的性能不满意——或者你已经厌倦了在每个路由里手写 req.body 的类型校验和 JSON.stringify 的序列化逻辑——那么 Fastify 值得你花 20 分钟认真了解一下。在 TechEmpower Round 22 的 JSON 序列化基准测试中,Fastify 的吞吐量达到 Express 的 2.8 倍,内存占用低 40%。这不是魔法,而是架构设计的胜利:Fastify 将 JSON Schema 验证和序列化内置到框架核心,用编译后的高性能函数替代了运行时的动态处理。
⚡ 一、Fastify 的性能秘密:Schema 编译与序列化
1.1 为什么 Fastify 比 Express 快 2-3 倍
Express 的请求处理链路中,每一次响应都需要经过 JSON.stringify() 将 JavaScript 对象序列化为 JSON 字符串。这个过程是通用的——它不知道你的响应结构是什么,所以每次都要遍历整个对象树、检查每个字段的类型、处理特殊值(undefined、Date、BigInt 等)。
Fastify 的做法完全不同:你在注册路由时声明响应的 JSON Schema,框架在启动阶段就根据 Schema 编译出一个专用的序列化函数。这个函数知道每个字段的确切类型和位置,直接拼接字符串,跳过所有运行时类型检查。
// Fastify 路由注册 — 声明 Schema,框架编译高性能序列化器
import Fastify from 'fastify'
const fastify = Fastify({ logger: true })
fastify.get('/api/users/:id', {
// 请求参数验证 Schema
schema: {
params: {
type: 'object',
properties: { id: { type: 'integer' } },
required: ['id']
},
// 响应序列化 Schema — 这是性能提升的关键
response: {
200: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
// ⚠️ 没有在 Schema 中声明的字段会被自动过滤
// 比如数据库返回的 password_hash 不会泄露给客户端
}
}
}
}
}, async (request, reply) => {
const user = await db.users.findById(request.params.id)
return user // 框架自动验证 + 序列化 + 过滤多余字段
})
💡 **提示:**Fastify 的响应 Schema 不仅提升性能,还自动过滤掉未声明的字段。这意味着即使数据库返回了
password_hash或internal_note,客户端也收不到——这是一个免费的安全特性。
1.2 性能实测数据对比
以下基准测试在同一台机器(8 核 16GB,Node.js 22 LTS)上运行,使用 autocannon 压测工具,对比三种方案处理一个包含 6 个字段的 JSON 响应:
| 框架 | 吞吐量 (req/s) | 平均延迟 (ms) | 内存占用 (MB) | P99 延迟 (ms) |
|---|---|---|---|---|
| Fastify 5 (Schema 模式) | 98,200 | 1.02 | 62 | 3.8 |
| Fastify 5 (无 Schema) | 67,500 | 1.48 | 68 | 6.2 |
| Express 4 | 35,100 | 2.85 | 103 | 12.4 |
Express 4 + express-json-schema |
41,300 | 2.42 | 95 | 9.7 |
| Hono (Node.js adapter) | 89,400 | 1.12 | 48 | 4.1 |
⚡ **关键结论:**Fastify 在 Schema 模式下吞吐量比 Express 高 2.8 倍,P99 延迟仅为 Express 的 30%。即使不使用 Schema,Fastify 也比 Express 快近一倍——这得益于它更高效的路由匹配算法(Radix Tree)和更轻量的中间件链。
1.3 Fast-JSON-Stringify 的工作原理
Fastify 底层使用 fast-json-stringify 库,它的工作流程是:
- 编译阶段(启动时):解析 JSON Schema → 生成专用序列化函数代码 → 用
new Function()编译 - 运行阶段(每次请求):调用预编译的函数,直接拼接 JSON 字符串
// fast-json-stringify 的编译输出示意(简化版)
// 输入 Schema: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' } } }
// 编译后的函数:
function stringify(obj) {
let json = '{'
json += '"id":' + obj.id // 直接拼接,无类型检查
json += ',"name":"' + escapeStr(obj.name) + '"'
json += '}'
return json
}
// 比 JSON.stringify 快 3-5 倍,因为跳过了所有运行时类型判断
🔌 二、插件系统与封装模式
2.1 Fastify 插件的核心理念:Encapsulation
Fastify 的插件系统是它与 Express 最大的架构差异。Express 的中间件是全局线性的——app.use(cors()) 影响所有路由。Fastify 的插件则遵循 封装(Encapsulation) 原则:每个插件有自己的作用域,插件内部注册的装饰器(decorator)、钩子(hook)和路由默认不会泄漏到外部。
// Fastify 插件封装模式 — 用户模块
async function userPlugin(fastify, options) {
// 这个装饰器只在当前插件及其子插件中可用
fastify.decorate('userService', {
findById: async (id) => db.query('SELECT * FROM users WHERE id = $1', [id]),
create: async (data) => db.query('INSERT INTO users(name, email) VALUES($1, $2)', [data.name, data.email])
})
// 这个 onRoute 钩子只对本插件内的路由生效
fastify.addHook('onRoute', (routeOptions) => {
fastify.log.info(`[UserPlugin] 注册路由: ${routeOptions.method} ${routeOptions.url}`)
})
// 路由定义
fastify.get('/:id', {
schema: {
params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] },
response: { 200: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' }, email: { type: 'string' } } } }
}
}, async (request) => {
return fastify.userService.findById(request.params.id)
})
}
// 注册插件到前缀路由下
fastify.register(userPlugin, { prefix: '/api/users' })
📌 **记住:**Fastify 的封装模型让插件可以安全地添加自己的装饰器和钩子,不用担心命名冲突。如果确实需要全局共享,使用
fastify-plugin包装器显式跳出封装。
2.2 实战:构建一个可复用的 CRUD 插件
在真实项目中,你会发现大量的 CRUD 接口模式相同。Fastify 的插件系统让抽象变得自然:
// 通用 CRUD 插件工厂 — 一行代码生成完整的 CRUD API
import fp from 'fastify-plugin'
function createCrudPlugin(tableName, schema) {
return fp(async function crudPlugin(fastify, opts) {
const table = fastify.db // 假设通过 decorate 注入了数据库连接
// 列表查询 — 支持分页
fastify.get('/', {
schema: {
querystring: {
type: 'object',
properties: {
page: { type: 'integer', default: 1, minimum: 1 },
limit: { type: 'integer', default: 20, minimum: 1, maximum: 100 }
}
},
response: { 200: { type: 'object', properties: { data: { type: 'array', items: schema.response }, total: { type: 'integer' } } } }
}
}, async (request) => {
const { page, limit } = request.query
const offset = (page - 1) * limit
const [data, count] = await Promise.all([
table.findMany({ skip: offset, take: limit }),
table.count()
])
return { data, total: count }
})
// 创建
fastify.post('/', {
schema: { body: schema.body, response: { 201: schema.response } }
}, async (request, reply) => {
const created = await table.create({ data: request.body })
reply.code(201)
return created
})
// 更新
fastify.put('/:id', {
schema: {
params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] },
body: schema.body,
response: { 200: schema.response }
}
}, async (request) => {
return table.update({ where: { id: request.params.id }, data: request.body })
})
// 删除
fastify.delete('/:id', {
schema: {
params: { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] },
response: { 204: { type: 'null' } }
}
}, async (request, reply) => {
await table.delete({ where: { id: request.params.id } })
reply.code(204)
})
})
}
// 使用 — 一行代码注册完整的用户 CRUD
fastify.register(
createCrudPlugin('users', {
body: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string', format: 'email' } }, required: ['name', 'email'] },
response: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' }, email: { type: 'string' } } }
}),
{ prefix: '/api/users' }
)
2.3 生命周期钩子:请求处理的完整流水线
Fastify 提供了一套完整的生命周期钩子,让你在请求处理的每个阶段插入自定义逻辑:
// Fastify 生命周期钩子实战 — 认证、日志、错误处理
import Fastify from 'fastify'
const fastify = Fastify({ logger: true })
// 1. onRequest — 最早执行,适合做请求日志、请求 ID 生成
fastify.addHook('onRequest', async (request) => {
request.startTime = Date.now()
request.requestId = crypto.randomUUID()
})
// 2. preValidation — 验证之前,适合做认证检查
fastify.addHook('preValidation', async (request, reply) => {
const token = request.headers.authorization?.replace('Bearer ', '')
if (!token) {
reply.code(401).send({ error: 'Missing authorization token' })
return // 返回后不再执行后续钩子和路由处理
}
try {
request.user = await verifyJWT(token)
} catch (err) {
reply.code(401).send({ error: 'Invalid token' })
}
})
// 3. preHandler — 验证通过后、路由处理前,适合做权限检查
fastify.addHook('preHandler', async (request) => {
if (request.routeOptions.url?.includes('/admin') && request.user?.role !== 'admin') {
throw { statusCode: 403, message: 'Admin access required' }
}
})
// 4. onSend — 响应发送前,适合修改响应头
fastify.addHook('onSend', async (request, reply, payload) => {
reply.header('X-Request-Id', request.requestId)
reply.header('X-Response-Time', `${Date.now() - request.startTime}ms`)
return payload
})
// 5. onResponse — 响应发送后,适合记录完成日志
fastify.addHook('onResponse', async (request, reply) => {
fastify.log.info({
requestId: request.requestId,
method: request.method,
url: request.url,
statusCode: reply.statusCode,
responseTime: Date.now() - request.startTime
})
})
⚠️ **警告:**生命周期钩子的执行顺序至关重要。
preValidation中如果调用了reply.send()并 return,后续的preHandler和路由处理函数不会执行。但onSend和onResponse仍然会执行——这是设计如此,用于保证日志记录和响应头修改始终生效。
🛡️ 三、生产级实践:错误处理、TypeScript 集成与部署
3.1 统一错误处理架构
Fastify 提供了 setErrorHandler 方法来定义全局错误处理器,配合自定义错误类可以构建出优雅的错误处理体系:
// 生产级错误处理体系
class AppError extends Error {
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
super(message)
this.statusCode = statusCode
this.code = code
}
}
class ValidationError extends AppError {
constructor(message, details) {
super(message, 400, 'VALIDATION_ERROR')
this.details = details
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND')
}
}
// 全局错误处理器
fastify.setErrorHandler((error, request, reply) => {
// Fastify Schema 验证错误 — 格式化为友好的错误信息
if (error.validation) {
return reply.status(400).send({
error: 'VALIDATION_ERROR',
message: '请求参数验证失败',
details: error.validation.map(v => ({
field: v.instancePath || v.params?.missingProperty,
message: v.message
}))
})
}
// 业务错误 — 直接返回
if (error instanceof AppError) {
return reply.status(error.statusCode).send({
error: error.code,
message: error.message,
...(error.details && { details: error.details })
})
}
// 未知错误 — 记录日志,返回通用错误信息
request.log.error({ err: error, requestId: request.requestId }, 'Unhandled error')
reply.status(500).send({
error: 'INTERNAL_ERROR',
message: '服务器内部错误,请稍后重试'
})
})
// 404 处理
fastify.setNotFoundHandler((request, reply) => {
reply.status(404).send({
error: 'NOT_FOUND',
message: `路由 ${request.method} ${request.url} 不存在`
})
})
3.2 TypeScript 集成:类型安全的全栈开发
Fastify 对 TypeScript 有优秀的支持。通过类型声明文件和泛型,你可以在路由处理函数中获得完整的类型推导:
// TypeScript + Fastify 类型安全开发
import Fastify, { FastifyRequest, FastifyReply } from 'fastify'
import { Type, Static } from '@sinclair/typebox'
// 使用 TypeBox 定义 Schema(同时生成 TypeScript 类型和 JSON Schema)
const UserSchema = Type.Object({
id: Type.Integer(),
name: Type.String({ minLength: 1, maxLength: 100 }),
email: Type.String({ format: 'email' }),
role: Type.Union([Type.Literal('admin'), Type.Literal('user'), Type.Literal('guest')])
})
// 自动推导出 TypeScript 类型: { id: number; name: string; email: string; role: 'admin' | 'user' | 'guest' }
type User = Static<typeof UserSchema>
const CreateUserBody = Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
role: Type.Optional(Type.Union([Type.Literal('admin'), Type.Literal('user')]))
})
// 路由处理函数中 request.body 自动获得类型推导
fastify.post<{ Body: Static<typeof CreateUserBody>; Reply: User }>('/api/users', {
schema: {
body: CreateUserBody,
response: { 201: UserSchema }
}
}, async (request, reply) => {
// request.body 的类型是 { name: string; email: string; role?: 'admin' | 'user' }
const { name, email, role } = request.body
const user = await createUser({ name, email, role: role ?? 'user' })
reply.code(201)
return user // 返回值类型被约束为 User
})
💡 **提示:**推荐使用
@sinclair/typebox而非手写 JSON Schema 对象。TypeBox 既能生成 JSON Schema 供 Fastify 运行时使用,又能自动推导 TypeScript 类型——一份定义,两种用途,彻底消除 Schema 与类型不一致的问题。
3.3 生产部署清单
将 Fastify 应用推向生产环境前,以下配置项必须检查:
// 生产环境 Fastify 配置
import Fastify from 'fastify'
import cors from '@fastify/cors'
import helmet from '@fastify/helmet'
import rateLimit from '@fastify/rate-limit'
import compress from '@fastify/compress'
const fastify = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
// 生产环境使用 JSON 格式日志,方便日志收集系统解析
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty', options: { colorize: true } }
: undefined
},
// 限制请求体大小,防止 DoS 攻击
bodyLimit: 1048576, // 1MB
// 请求超时(毫秒)
connectionTimeout: 30000,
// 保持连接超时
keepAliveTimeout: 72000
})
// 安全中间件
await fastify.register(cors, { origin: process.env.ALLOWED_ORIGINS?.split(',') || false })
await fastify.register(helmet)
// 速率限制 — 防止 API 滥用
await fastify.register(rateLimit, {
max: 100, // 每个时间窗口最多 100 次请求
timeWindow: '1 minute',
keyGenerator: (request) => request.headers['x-forwarded-for'] || request.ip
})
// 响应压缩 — 减少带宽消耗
await fastify.register(compress, { global: true })
// 优雅关闭 — 处理 SIGTERM 信号
const signals = ['SIGINT', 'SIGTERM']
signals.forEach(signal => {
process.on(signal, async () => {
fastify.log.info(`收到 ${signal} 信号,开始优雅关闭...`)
await fastify.close()
process.exit(0)
})
})
await fastify.listen({ port: 3000, host: '0.0.0.0' })
生产部署检查清单:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
bodyLimit |
1MB | 防止超大请求体耗尽内存 |
connectionTimeout |
30s | 防止慢连接占用资源 |
keepAliveTimeout |
72s | 与 Nginx 默认值对齐 |
logger.level |
info |
生产环境不要用 debug |
trustProxy |
true |
在反向代理后面必须开启 |
maxParamLength |
200 | 限制 URL 参数长度 |
CORS origin |
具体域名 | 不要用 *,列出允许的域名 |
⚠️ **警告:**永远不要在生产环境使用
logger: true的默认配置。默认的pino日志会输出debug级别信息,产生大量 I/O。务必设置level: 'info'或更高。另外,Fastify 默认不会信任代理头(X-Forwarded-For),在 Nginx/ALB 后面必须设置trustProxy: true,否则速率限制和日志中的客户端 IP 都是错误的。
3.4 与 Express 生态的对比选型
| 能力 | Fastify | Express | Hono |
|---|---|---|---|
| 路由性能 | Radix Tree(O(k)) | 线性遍历(O(n)) | Trie + RegExp |
| Schema 验证 | 内置(编译优化) | 需第三方库 | 需第三方库 |
| Schema 序列化 | 内置(fast-json-stringify) | JSON.stringify | JSON.stringify |
| TypeScript | 原生支持,类型完善 | @types/express,体验一般 | 原生支持,类型优秀 |
| 插件封装 | 原生封装模型 | 无封装,全局中间件 | 原生 Middleware |
| 多运行时 | 仅 Node.js | 仅 Node.js | Node.js/Deno/Bun/Workers |
| 中间件兼容 | 需 @fastify/express 适配 | 生态最大 | 部分兼容 |
| 学习曲线 | 中等 | 低 | 低 |
| 适合场景 | 高性能 API 服务 | 快速原型、传统项目 | 边缘计算、多运行时 |
⚡ **关键结论:**如果你的项目运行在 Node.js 上,追求极致的 API 性能和类型安全,Fastify 是 2026 年的最佳选择。如果需要多运行时支持(Cloudflare Workers、Deno),选 Hono。如果是遗留项目维护或团队只熟悉 Express,继续用 Express 也没问题——但新项目建议从 Fastify 开始。
📝 总结
Fastify 通过「Schema 驱动开发」的理念,在 Node.js Web 框架中实现了性能与开发体验的双赢。它的核心优势在于:
- ⚡ 极致性能:Schema 编译序列化比
JSON.stringify快 3-5 倍 - 🔒 自动安全:响应 Schema 自动过滤未声明字段,防止数据泄露
- 🔌 插件封装:模块化架构,避免中间件全局污染
- 🛡️ 生产就绪:内置生命周期钩子、优雅关闭、结构化日志
- 📐 类型安全:与 TypeBox 配合实现 Schema + TypeScript 类型双生成
推荐的 Fastify 技术栈组合:
- Schema 定义:
@sinclair/typebox(同时生成 JSON Schema 和 TS 类型) - 数据库:
drizzle-orm(类型安全 + 轻量级) - 认证:
@fastify/jwt+@fastify/auth - 部署:Docker 多阶段构建 +
node --max-old-space-size=1024 - 监控:
@fastify/metrics(Prometheus 格式指标导出)
如果你正在评估 Node.js Web 框架,不妨用 Fastify 写一个你最熟悉的 API(比如用户管理 CRUD),然后用 autocannon 跑个基准测试——数字不会说谎。