Fastify 实战:用高性能 Node.js 框架构建 API 服务

在 Node.js 生态里,Fastify 是近年来最受欢迎的高性能 Web 框架之一。它主打”快”——官方基准显示吞吐可达 Express 的数倍,同时内置 JSON Schema 校验、插件化和优秀的 TypeScript 支持。如果你正在用 Node.js 写 API 服务,又苦于 Express 的样板代码和性能瓶颈,Fastify 值得一试。Fastify 由 Node.js 核心贡献者 Matteo Collina 主导,最初的目标就是弥补 Express 在现代多核硬件下的性能短板:它把路由匹配、参数校验、响应序列化都做成了”编译期预生成”的函数,而不是每次请求都重新解释。本文从零搭建一个可上生产的 API 服务,覆盖路由、校验、插件、钩子、容器化部署与持续集成,让你跑通一条完整链路。

一、为什么是 Fastify:和 Express、Hono 怎么选

选框架要看三点:性能、开发体验、生态成熟度。下面把三个主流 Node.js Web 框架放在同一维度对比,方便你按场景取舍。Express 仍是存量最大的框架,但它在每个请求上都要走层层中间件、缺乏原生校验,随着 Node 20 之后事件循环本身的优化,框架调度开销在总耗时里的占比越来越明显。Fastify 通过预编译路由、复用序列化函数把这部分开销压到极低;Hono 则走”跨运行时”路线,同一份代码能跑在 Node、Bun、Deno 甚至边缘节点上。

维度ExpressFastifyHono
吞吐(相对)1x2-3x2-4x(跨运行时)
请求体校验需额外中间件内置 JSON Schema内置 Validator
插件体系中间件模式encapsulation 插件中间件 + 适配层
TypeScript社区类型一等支持一等支持
学习曲线平缓中等中等

结论很直接:追求极限吞吐与开箱即用的校验,选 Hono 与 Fastify 这类现代框架;老项目迁移成本敏感就留在 Express。Fastify 的甜点在于”接近原生速度的路由 + 内置 Schema 校验”,不用再拼装一大堆中间件。

除了性能,Fastify 还有一个常被低估的优势:官方维护了大量第一方插件(@fastify/*),从静态文件、CORS、JWT 到速率限制一应俱全,质量与框架本身同步发布,省去在 npm 上淘第三方半成品的踩坑成本。

二、五分钟起步:跑起第一个服务

新建一个目录,初始化并安装 Fastify,然后写入口文件。下面是最精简的可运行示例:

# 初始化并安装
npm init -y
npm install fastify

# server.js
const fastify = require('fastify')({ logger: true })

fastify.get('/', async (request, reply) => {
  return { hello: 'Fastify', ts: Date.now() }
})

const start = async () => {
  try {
    await fastify.listen({ port: 3000, host: '0.0.0.0' })
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

启动后访问 http://localhost:3000 即可看到 JSON 响应。注意 host: '0.0.0.0',否则容器环境外部无法访问——这是新手最常踩的坑之一。

Fastify 默认集成了 Pino 日志,比 console.log 快一个数量级,且输出为结构化 JSON,方便后续接入日志系统做检索与告警;生产环境记得把日志级别交给环境变量控制,避免调试日志污染线上。

三、路由与 JSON Schema 校验

Fastify 内置 Ajv,可以直接在路由上声明请求/响应 Schema,自动完成参数校验与序列化,错误会返回标准 400。下面演示一个带校验的 POST 接口:

fastify.post('/user', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'age'],
      properties: {
        name: { type: 'string', minLength: 1 },
        age:  { type: 'integer', minimum: 0, maximum: 150 }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: { id: { type: 'string' }, ok: { type: 'boolean' } }
      }
    }
  }
}, async (request, reply) => {
  const { name, age } = request.body
  // 这里写你的业务逻辑,body 已被校验
  return { id: crypto.randomUUID(), ok: true, name, age }
})

一旦请求体不符合 Schema,Fastify 会自动返回 400 并附带清晰的字段错误,省去了手写一堆 if (!req.body.xxx) 的校验代码。

Schema 的意义不止于校验:Fastify 会用它生成专用的高速序列化函数,响应时直接拼接字符串而非调用 JSON.stringify,这也是吞吐领先 Express 的关键细节之一;同时 200/400/500 各状态码都能预定义响应结构,前端对接更省心。

四、插件机制与依赖注入

Fastify 的插件通过 register 注册,并遵循”封装”原则:被注册插件 decorate 的内容默认只在当前作用域可见,避免全局污染。下面把数据库客户端做成可复用插件:

// plugins/db.js
const fp = require('fastify-plugin')
const { MongoClient } = require('mongodb')

module.exports = fp(async (fastify, opts) => {
  const client = new MongoClient(opts.uri)
  await client.connect()
  fastify.decorate('db', client.db(opts.dbName))
})
// 在入口中
fastify.register(require('./plugins/db'), { uri: process.env.MONGO_URI, dbName: 'app' })

注册后即可在任意 handler 里用 fastify.db 访问数据库。用 fastify-plugin 包裹可让 decorate 向父作用域透传,适合这类基础设施型插件。

封装(encapsulation)是 Fastify 区别于 Express 中间件的核心机制:子插件里 decorate 的内容默认不会向兄弟插件泄漏,天然避免命名冲突;只有用 fastify-plugin 包装过的才是真正的全局插件,这一点在多人协作的大项目里尤其重要。

五、生命周期钩子

// 请求级钩子:统一注入请求 ID 与计时
fastify.addHook('onRequest', async (request, reply) => {
  request.startAt = Date.now()
  request.id = crypto.randomUUID()
})

fastify.addHook('onResponse', async (request, reply) => {
  const cost = Date.now() - (request.startAt || Date.now())
  fastify.log.info({ id: request.id, ms: cost, status: reply.statusCode }, 'request done')
})

钩子可用于鉴权、日志、链路追踪、CORS 等横切逻辑。生产环境建议把这类钩子也封装成插件,保持入口文件清爽。

完整的请求生命周期还有 preParsing、preValidation、preHandler、onSend、onError 等钩子,它们按固定顺序串成一条管道,你可以在任意位置精确插入鉴权、限流、压缩或链路追踪注入等逻辑,而不必污染业务 handler。

六、TypeScript 类型安全

Fastify 对 TypeScript 支持一流。配合 @fastify/type-provider-typebox 可以让 Schema 与入参类型自动联动:

import Fastify from 'fastify'
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type } from '@sinclair/typebox'

const app = Fastify().withTypeProvider<TypeBoxTypeProvider>()

app.get('/item/:id', {
  schema: { params: Type.Object({ id: Type.String() }) }
}, async (req) => {
  // req.params.id 自动推断为 string
  return { id: req.params.id }
})

TypeBox 的 Schema 既充当运行时校验,又能在编译期推导出 req/reply 的类型,避免了为同一个数据结构维护两套定义的重复劳动;当你重构字段时,类型错误会在编辑阶段就被 TypeScript 标红,而不是等到线上才暴露。

七、用 Docker 容器化部署

把 Fastify 服务打进镜像非常简单。多阶段构建能显著减小体积,配合 Docker 容器化基础 即可快速上线:

# Dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

FROM node:20-alpine
WORKDIR /app
COPY --from=build /app /app
EXPOSE 3000
CMD ["node", "server.js"]

构建并运行:docker build -t my-api . && docker run -p 3000:3000 my-api。建议通过环境变量注入端口与连接串,保持镜像与配置解耦。

生产镜像建议改用非 root 用户运行,并加上 HEALTHCHECK 指令配合编排系统做就绪/存活探针;再叠加 Nginx 反向代理 做 TLS 终结与负载均衡,整套发布链路就完整了。

八、用 GitHub Actions 做 CI

提交即跑 lint + 测试,能挡掉大量低级错误。参考 GitHub Actions 流水线实践 的约定,加一个最小工作流:

# .github/workflows/ci.yml
name: ci
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm test

CI 里可以顺手加上 npm audit 与依赖缓存,在照顾速度的同时把安全隐患挡在合并之前;把测试门槛设为合并必需的检查项,能避免把未经验证的代码推到 main 分支。

九、性能调优与三个常见坑

  • 开启 keep-alive:反向代理(如 Nginx)与 Fastify 之间保持长连接,避免每请求建连开销。在 4 核以上机器上,连接复用带来的提升往往比任何代码优化都更直接;同时给反代开启 HTTP/2 能进一步降低延迟。详见 Nginx 反向代理配置。
  • 控制 body 上限:用 bodyLimit 限制请求体大小(默认 1MB),上传场景按需调大但不要无上限,配合超时能挡掉大部分慢速攻击与内存打爆。
  • 生产关掉开发日志:logger 在压测/生产下建议降级;Pino 本身很快,真正拖慢的是同步写入,生产应改用异步传输或集中式日志收集。

十、小结

Fastify 用”快 + 内置校验 + 强插件”三件套,把 Node.js API 服务的样板代码压到最低。从 fastify() 起步,到 Schema 校验、插件注入、钩子、TypeScript 类型、Docker 部署与 CI,一条龙即可交付一个高性能、可维护的服务。在 VS Code 里配合 REST Client 插件联调,开发体验非常顺滑。下一步可以接入数据库、缓存与链路追踪,把它打磨成完整的生产级后端。当你把校验、插件、钩子与类型串起来后,一个 Fastify 服务就已经具备生产雏形;再补上监控与告警,就能放心交给线上流量,这也是现代 Node.js 后端最稳妥的起手式。

上一篇 Nginx 504 Gateway Timeout 排查实战
下一篇 技术复盘实战:用无指责复盘把事故变成团队资产
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]