人人都会AI编程

12.4 Fastify

更新时间:2026-07-10

在前几节中,我们分别介绍了 Express 的中间件机制、Koa 的洋葱模型和 NestJS 的企业级架构。这些框架各有侧重,但如果你追求极致的请求吞吐量、低开销的 JSON 处理以及高度模块化的插件体系,Fastify 是一个必须认真考虑的选项。它并不是对 Express 的简单模仿,而是在底层设计上做了大量性能优化,同时保持了开发体验的愉悦感。

12.4.1 Fastify 的设计哲学

Fastify 由 Matteo Collina 和 Tomas Della Vedova 创建,核心目标是在不牺牲开发体验的前提下,最大化 HTTP 服务的处理速度。根据官方基准测试,Fastify 的吞吐量可以达到 Express 的 2-3 倍,在某些简单路由场景中甚至更高。

这一性能优势并非来自某个单一的魔法优化,而是多个层面的协同设计:

  • 快速 JSON 序列化:Fastify 使用 fast-json-stringify 库,根据 JSON Schema 预先编译出最优的序列化函数,比通用的 JSON.stringify 快数倍。
  • 低开销的路由匹配:采用 Radix Tree 数据结构存储路由,路由查找时间复杂度接近 O(k)(k 为路径长度),即使注册数百个路由也能保持稳定的匹配性能。
  • 全异步插件体系:插件加载、路由注册、生命周期钩子全部基于 Promise,启动速度快且资源占用低。
  • 高效的请求/响应对象:内部对 Node.js 原生的 reqres 做了轻量封装,避免不必要的属性访问和对象创建。

与 Express 不同,Fastify 并不追求成为“最小化框架”,它内置了日志系统(基于 Pino)、请求校验、序列化优化和安全头处理,同时通过清晰的插件 API 确保扩展性不受影响。

12.4.2 快速开始:一个最小的 Fastify 服务

使用 Fastify 搭建一个 HTTP 服务极其简单。初始化项目后安装依赖:

npm init -y
npm install fastify

然后创建 server.js

const fastify = require('fastify')({ logger: true });

// 声明路由
fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});

// 启动服务
const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
    console.log('服务器运行在 http://localhost:3000');
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
start();

与 Express 不同,Fastify 的 listen 返回一个 Promise,并默认在 0.0.0.0 上监听。路由处理函数可以直接返回一个 JavaScript 对象,Fastify 会自动将其序列化为 JSON 响应,并设置 Content-Type: application/json。这种便捷性减少了样板代码,同时享受了内置的快速序列化。

12.4.3 路由与请求校验

Fastify 的路由注册支持与 Express 类似的路径模式,但额外提供了输入校验这一重要特性。通过为每个路由定义 JSON Schema,Fastify 能够在运行时自动校验请求的头部、查询参数、路径参数和请求体,并在校验失败时返回结构化的错误信息。

const opts = {
  schema: {
    params: {
      type: 'object',
      properties: { id: { type: 'integer' } },
      required: ['id']
    },
    querystring: {
      type: 'object',
      properties: { page: { type: 'integer', default: 1 } }
    },
    body: {
      type: 'object',
      properties: {
        name: { type: 'string', minLength: 1 },
        email: { type: 'string', format: 'email' }
      },
      required: ['name', 'email']
    }
  }
};

fastify.post('/user/:id', opts, async (request, reply) => {
  const { id } = request.params;    // 类型为 number(已校验)
  const { page } = request.query;   // 默认值 1 已生效
  const { name, email } = request.body;
  // 业务逻辑...
  return { id, page, name, email };
});

这种声明式的校验机制带来了多个好处:

  • 安全性增强:输入在进入业务逻辑前已经被严格过滤,减少了注入攻击和参数篡改的风险。
  • 自动生成文档:Fastify 的 JSON Schema 与 OpenAPI/Swagger 天然对齐,配合 @fastify/swagger 插件可以自动生成 API 文档,无需额外维护文档注释。
  • 序列化优化复用:定义的输出 Schema 会被 fast-json-stringify 用于编译序列化函数,进一步加速响应生成。

12.4.4 插件体系与生命周期

Fastify 采用完全基于 Promise 的插件架构。插件是一个接收 fastify 实例、选项(options)和 done 回调(可选)的函数,通过 fastify.register() 注册。插件可以装饰实例、添加路由、注册子插件,并通过 AVL 树结构封装作用域,避免全局污染。

// 定义一个插件
async function myPlugin(fastify, options) {
  fastify.decorate('utility', () => 'useful');
  fastify.get('/plugin-route', async () => ({ status: 'ok' }));
}

// 将插件注册到特定作用域(前缀 /api)
fastify.register(myPlugin, { prefix: '/api' });

Fastify 的插件加载遵循明确的父子关系,每个插件可以拥有自己的错误处理、生命周期钩子和配置项。常见的插件如 @fastify/cors(跨域处理)、@fastify/static(静态文件服务)、@fastify/jwt(JWT 认证)都是以这种方式封装,使用体验一致。

Fastify 的请求生命周期也提供了丰富的钩子(Hook),开发者可以在请求的不同阶段插入自定义逻辑:

  • onRequest:请求到达时(在路由匹配之前)
  • preParsing:原始 body 解析前
  • preValidation:路由 Schema 校验前
  • preHandler:即将执行业务处理函数前
  • onSend:响应发出前
  • onResponse:响应已发送后

这些钩子同样通过 fastify.addHook() 注册,可以是全局或插件作用域内的。例如,利用 preHandler 编写一个简单的认证守卫:

fastify.addHook('preHandler', async (request, reply) => {
  if (!request.headers.authorization) {
    reply.code(401).send({ error: 'Unauthorized' });
  }
});

这种生命周期的设计使得 Fastify 在保持核心库体积小巧的同时,能够通过插件实现复杂的业务中间件需求。

12.4.5 日志与错误处理

Fastify 内置的日志系统基于 Pino,这是 Node.js 生态中性能最高的日志库之一。日志记录默认在调试环境中以彩色、可读格式输出,在生产环境中可配置为 JSON 行,方便集成到日志收集系统。

使用方式非常简单:

fastify.get('/', async (request, reply) => {
  request.log.info({ user: 'alice' }, '处理请求');
  // 业务逻辑...
});

日志实例通过 request.log 传递,可以自动携带请求 ID、HTTP 方法、URL 等上下文,这对排查分布式系统中的请求链路非常有帮助。

Fastify 也定义了系统的错误处理机制:任何路由或钩子中抛出的错误,都会被框架捕获并自动生成符合 JSON Schema 的错误响应。开发者也可以自定义错误处理函数:

fastify.setErrorHandler((error, request, reply) => {
  // 记录错误日志
  request.log.error(error);
  // 自定义响应格式
  reply.status(500).send({ code: 'INTERNAL_ERROR', message: '发生内部错误' });
});

这种全局错误处理确保了所有异常都有统一的出口,避免未捕获错误导致进程崩溃。

12.4.6 性能优势背后的原理

Fastify 的性能并非吹嘘,而是源于几个具体的架构决策:

  1. Radix Tree 路由:相比 Express 的线性路由匹配,Radix Tree 将路由路径拆分为字符节点,查找效率更高,尤其适合注册大量 RESTful 路由的微服务。
  2. 轻量级封装:Fastify 的 RequestReply 对象仅添加了必要的属性和方法,没有像 Express 那样通过原型链扩展全部 Node.js 原生对象的所有属性,从而减少了属性访问开销。
  3. 提前编译 Schema:JSON Schema 验证器(ajv)和序列化器(fast-json-stringify)都采用提前编译技术,将运行时解析的逻辑转化为高效的机器码。
  4. 复用 Buffer:内部字符串拼接和响应体构建尽可能使用预分配 Buffer,减少内存分配和 GC 压力。

实际测试中,这些优化能显著提升 QPS(每秒查询数)并降低尾延迟。对于高流量的 API 网关、物联网数据采集接口或低延迟交易系统,Fastify 的性能优势可以转化为直接的硬件成本节约。

12.4.7 与 Express / Koa 的核心差异

很多团队从 Express 迁移到 Fastify 时,最关心开发体验的相似度。Fastify 确实借鉴了 Express 的路由声明风格,但内在设计有根本区别:

| 特性 | Express | Koa | Fastify |
|------|---------|-----|---------|
| 路由匹配 | 线性迭代 | 无内置路由 | Radix Tree |
| 请求校验 | 依赖第三方库 | 依赖第三方库 | JSON Schema 内置 |
| JSON 序列化 | 通用 JSON.stringify | 通用 JSON.stringify | 编译类型优化 |
| 中间件模型 | 线性流水线 | 洋葱模型 | 生命周期钩子 + 插件 |
| 类型支持 | 需要 @types/express | 需要 @types/koa | 原生 TypeScript |
| 日志 | 无内置 | 无内置 | 集成 Pino |
| 插件体系 | 无强约束 | 无强约束 | 封装式插件树 |

迁移建议:如果你的应用是轻量级原型或单一的 REST 服务,Express 足够简单;如果你需要更灵活的中间件控制和较小的核心体积,Koa 更合适;如果你的场景对吞吐量(QPS)有高要求,或者需要使用 JSON Schema 自动生成文档、在编译时进行类型检查,Fastify 是最佳选择。

在 TypeScript 使用上,Fastify 提供了完整的类型定义和推断,路由处理函数的 requestreply 可以自动推断出包含自定义装饰器的类型,不需要像 Express 那样手动扩展 Request 接口。

12.4.8 Fastify 生态与企业级使用

Fastify 的官方和社区插件生态已非常完善,覆盖了常见的企业级需求:

  • @fastify/swagger:自动生成 OpenAPI 规范文档。
  • @fastify/swagger-ui:在开发环境中提供可视化接口测试页面。
  • @fastify/jwt:JWT 认证与验证插件。
  • @fastify/rate-limit:请求限流插件。
  • @fastify/caching:基于 Redis 的缓存插件。
  • @fastify/static:静态资源服务。
  • @fastify/websocket:WebSocket 支持。

另外,Fastify 可以作为 NestJS 的底层 HTTP 适配器,替代默认的 Express。只需要在创建 Nest 应用时传入 FastifyAdapter,即可获得 Fastify 的高性能特性与 NestJS 的架构优势。

import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter()
  );
  await app.listen(3000);
}
bootstrap();

这种组合在企业级应用中并不少见:NestJS 提供模块化与依赖注入,Fastify 提供性能底衬,二者各取其长。

12.4.9 使用 Fastify 的注意事项

尽管 Fastify 的设计很优秀,但在实际使用时还是需要留意几点:

  • 响应必须明确:如果路由处理函数返回了一个普通对象,Fastify 默认将其作为 JSON 响应;如果不返回任何值(undefined),可能导致挂起。如果需要无 body 的响应(如 204),应该显式调用 reply.send() 或返回 '' / null
  • 插件封装作用域:由于 Fastify 的插件作用域隔离,装饰器和钩子是封装的。如果你希望某些功能在所有插件中可用,应该在最外层插件或使用 fastify.decorateReply 时注意作用域。
  • 异步闭包内存:与任何 Node.js 框架一样,要避免在生命周期钩子中捕获大量闭包变量,以减少内存泄漏风险。
  • 兼容性:Fastify 的版本更新较快(当前为 v4),虽然 API 设计趋于稳定,但引入时仍建议锁定主版本并参考迁移指南。

12.4.10 小结

Fastify 在 Node.js Web 框架生态中定位为一个性能优先、插件化、内置高效工具集的现代化框架。它通过 Radix Tree 路由、编译模板 Schema、零日志开销和轻量封装,在 I/O 密集型场景中提供了超出传统 Express 数倍的吞吐能力。同时,JSON Schema 驱动输入校验和序列化,减少了手工参数检查的负担,也为 API 文档自动生成铺平了道路。

对于新项目或追求性能优化的团队来说,Fastify 是一个值得第一时间评估的选项。在后续章节中,我们将进一步对比这四个框架,帮助你在实际选型中做出更准确的决策。