Fastify 高性能框架:Schema 验证与插件化

FreeGuideOnline 最新 2026-06-15

Fastify 高性能框架:Schema 验证与插件化

Fastify 是专为高性能而生的 Node.js Web 框架,它在保证极高吞吐量的同时,通过内置的 Schema 验证和强大的插件系统提供了严谨的开发体验。本教程将带你从零开始,理解 Fastify 的核心思想,并掌握 Schema 验证与插件化的实战用法。

为什么选择 Fastify?

Fastify 的设计目标非常明确:在提供优秀开发者体验的前提下,成为最快的 Web 框架之一。它的核心优势包括:

  • 极致的性能:在各类基准测试中表现卓越,得益于其高度优化的路由、序列化、验证机制。
  • 开箱即用的 Schema 验证:使用 JSON Schema 对请求和响应进行快速验证与序列化,既安全又高效。
  • 强大的插件体系:通过插件机制封装逻辑、扩展功能,代码结构清晰,复用性强。
  • 原生支持 TypeScript:虽然核心用 JavaScript 编写,但提供了完善的类型定义。

环境准备

确保你的开发环境已安装 Node.js 18+ 版本。然后新建项目并安装 Fastify:

mkdir fastify-tutorial
cd fastify-tutorial
npm init -y
npm install fastify

创建第一个 Fastify 服务器

Fastify 的最小服务器只需几行代码。创建一个 app.js 文件:

// app.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();

运行 node app.js,访问 http://localhost:3000,你将看到 {"hello":"world"}

日志与开发体验

在构建实例时传入 { logger: true } 会启用 Pino 日志,方便调试。生产环境中你可以配置不同的日志级别。

Schema 验证:让请求更安全

没有验证的接口容易产生不可预期的数据,而 Fastify 将 Schema 验证深度整合到了路由中。你只需定义 JSON Schema,框架会自动完成校验和序列化,极大提升性能。

基本用法:验证请求体(Body)

假设我们要接收一个创建用户的数据,要求 name 为字符串且必填,age 为整数。

fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name'],
      properties: {
        name: { type: 'string' },
        age: { type: 'integer' }
      }
    }
  },
  handler: async (request, reply) => {
    // request.body 已经被验证并保证了类型
    const { name, age } = request.body;
    // 这里可以放心写入数据库等操作
    return { result: 'ok', user: { name, age } };
  }
});

如果客户端发送了不合规的 JSON,例如 age"28",Fastify 会返回 400 错误,并附带清晰的错误信息:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body/age must be integer"
}

验证多种请求数据

Fastify 支持对 bodyqueryparamsheaders 分别定义 Schema。

fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    querystring: {
      type: 'object',
      properties: {
        detail: { type: 'boolean' }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' }
        }
      }
    }
  },
  handler: async (request, reply) => {
    const { id } = request.params;    // id 保证是整数
    const { detail } = request.query; // detail 保证是布尔值或 undefined
    // 从数据库获取用户...
    const user = { id, name: 'Alice' };
    return user;
  }
});

响应 Schema 特别重要:它不仅能验证返回数据,还会触发序列化优化。Fastify 会根据定义的 response Schema 直接用 fast-json-stringify 生成的高效函数序列化数据,比原生 JSON.stringify 快得多。

自定义错误处理

你可以在全局或路由级别自定义验证失败的处理逻辑。

fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    // error.validation 包含验证失败的详细信息
    reply.status(400).send({
      ok: false,
      message: '输入参数错误',
      details: error.validation
    });
  } else {
    reply.send(error);
  }
});

Schema 的复用与聚合

你可以将常用的 Schema 定义提取出来,通过 addSchema 添加共享 Schema,并使用 JSON Schema 的 $ref 引用。

fastify.addSchema({
  $id: 'user',
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'integer' }
  }
});

fastify.post('/users', {
  schema: {
    body: {
      $ref: 'user'
    },
    response: {
      201: {
        $ref: 'user'
      }
    }
  },
  handler: async (request, reply) => {
    // ...
    reply.code(201);
    return request.body;
  }
});

插件化架构:构建可维护的应用

插件是 Fastify 构建大型应用的基石。所有功能(路由、工具函数、数据库连接等)都可以封装成插件,并通过 register 进行注册。这带来了:

  • 逻辑封装:每个插件拥有独立的作用域,子实例可以继承或覆盖父实例的装饰、钩子。
  • 顺序保证:插件按注册顺序加载,如同洋葱模型一样包裹请求处理流程。
  • 易于测试:每个插件都可以独立测试。

编写一个简单的插件

插件是一个接受 fastify 实例和可选的配置对象作为参数的函数。

// plugins/db-connector.js
const fp = require('fastify-plugin');

async function dbConnector(fastify, opts) {
  // 模拟数据库连接
  const db = {
    users: {
      find: async (id) => ({ id, name: `User ${id}` })
    }
  };

  // 将数据库实例装饰到 fastify 上
  fastify.decorate('db', db);

  // 关闭钩子:应用退出时断开连接
  fastify.addHook('onClose', async (instance, done) => {
    // 模拟断开
    console.log('数据库连接已关闭');
  });
}

// 使用 fastify-plugin 包装,使其成为可直接注册的插件
module.exports = fp(dbConnector, {
  name: 'db-connector' // 插件名称,可选
});

注意 fastify-plugin 的用法:它不是必需的,但强烈推荐。使用 fp() 包装后,插件就不会创建一个新的作用域,而是直接向父实例添加装饰,避免了重复包装的作用域开销,尤其适用于数据库、工具类等需要暴露给全应用的插件。

注册并使用插件

// app.js
const fastify = require('fastify')({ logger: true });
const dbConnector = require('./plugins/db-connector');

fastify.register(dbConnector, {
  url: 'mongodb://localhost:27017/mydb' // 配置参数
});

fastify.get('/user/:id', async (request, reply) => {
  const { id } = request.params;
  // 使用插件装饰的 db 对象
  const user = await fastify.db.users.find(parseInt(id));
  return user;
});

fastify.listen({ port: 3000 }).then(() => {
  console.log('服务器已启动');
});

插件作用域与钩子

Fastify 的注册系统会创建父子实例关系。子插件可以添加自己的路由、钩子,甚至覆盖父实例的设置(如日志级别),但这些修改不会影响父实例。

通过 addHook,你可以在请求生命周期的各个阶段插入逻辑,例如:

  • onRequest:请求到达时,路由之前
  • preHandler:路由匹配后,实际处理函数之前
  • onSend:响应发送前
  • onResponse:响应已发送
fastify.addHook('onRequest', async (request, reply) => {
  // 身份验证检查示例
  if (!request.headers.authorization) {
    reply.code(401).send({ error: '未授权' });
  }
});

综合示例:用户管理 API

下面将 Schema 验证与插件结合,构建一个小型的用户 API。

目录结构

project/
  ├─ app.js
  ├─ plugins/
  │    └─ db.js
  └─ routes/
       └─ users.js

plugins/db.js – 模拟数据库插件:

const fp = require('fastify-plugin');
module.exports = fp(async (fastify, opts) => {
  const store = new Map();
  fastify.decorate('db', {
    create: (user) => { store.set(user.id, user); return user; },
    get: (id) => store.get(id),
    list: () => [...store.values()]
  });
});

routes/users.js – 用户路由插件,包含 Schema 定义:

const fp = require('fastify-plugin');

// 共享的用户 Schema
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string' },
    email: { type: 'string', format: 'email' }
  }
};

module.exports = fp(async (fastify, opts) => {
  fastify.addSchema({
    $id: 'user',
    ...userSchema
  });

  // 创建用户
  fastify.post('/', {
    schema: {
      body: {
        type: 'object',
        required: ['name', 'email'],
        properties: {
          name: { type: 'string' },
          email: { type: 'string', format: 'email' }
        }
      },
      response: {
        201: { $ref: 'user' }
      }
    },
    handler: async (request, reply) => {
      const id = Date.now(); // 模拟 ID 生成
      const user = { id, ...request.body };
      fastify.db.create(user);
      reply.code(201);
      return user;
    }
  });

  // 获取用户列表
  fastify.get('/', {
    schema: {
      response: {
        200: {
          type: 'array',
          items: { $ref: 'user' }
        }
      }
    },
    handler: async () => fastify.db.list()
  });
});

app.js – 组装应用:

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

// 注册数据库插件
fastify.register(require('./plugins/db'));

// 注册用户路由插件,并设置前缀
fastify.register(require('./routes/users'), { prefix: '/users' });

fastify.listen({ port: 3000 }).then((address) => {
  fastify.log.info(`服务器运行于 ${address}`);
});

现在你可以用 cURL 或 Postman 测试:

curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}'
# 返回: {"id":169384..., "name":"Alice","email":"alice@example.com"}

# 错误格式演示
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Bob","email":"not-an-email"}'
# 返回 400 错误及验证消息

curl http://localhost:3000/users
# 返回用户数组

进阶技巧

  • 序列化加速:尽量为路由定义 response Schema,Fastify 内部会缓存编译后的序列化函数,获得极大性能提升。

  • 封装验证规则:对常用的字符串格式(如 email、uri)可以定义自己的格式验证函数:

    fastify.addSchemaFormat('phone', (value) => /^1[3-9]\d{9}$/.test(value));
    
  • 插件顺序与依赖:如果你的插件依赖另一个插件,可以在 register 时传入 dependencies 选项,或者利用异步注册的顺序保证。

  • 使用 TypeScript:Fastify 提供了完善的泛型支持,你可以通过 FastifyRequest<{ Params: ..., Body: ... }> 获得精确的类型推导,与 Schema 配合简直是天作之合。

总结

Fastify 通过 Schema 优先 的验证方式和 模块化插件体系,完美平衡了性能与健壮性。作为 Node.js 开发者,拥抱 Fastify 意味着你可以用更少的代码实现更安全、更快速的服务。从今天开始,尝试将你的下一个 API 项目迁移到 Fastify,你将感受到它带来的巨大优势。