• 微信:WANCOME
  • 扫码加微信,提供专业咨询
  • 服务热线
  • 13215191218
    13027920428

  • 微信扫码访问本页
desc-2
环企首页

Fastify

概述

Fastify 是一个专注于高性能的 Node.js Web 框架,号称是目前最快的 HTTP 框架之一。核心特点包括:

  • 高吞吐量:基于 Node.js 原生 http 模块和 re2(正则引擎)优化,性能远高于 Express
  • 内置请求校验:通过 JSON Schema 自动验证请求参数,无需额外引入校验库
  • JSON 序列化优化:内置极速 JSON 序列化器,直接输出二进制而非字符串
  • 插件系统:每个插件都是独立的 Fastify 实例,支持嵌套和封装
  • 开发者体验:内置日志(pino)、提供 Swagger 文档生成、热重载等工具

适合场景:

  • 高并发 API 服务(QPS 敏感型)
  • 对响应延迟有严格要求的实时服务
  • 需要严格数据校验的 API
  • 微服务架构中的高性能节点

性能对比(大致参考):

Fastify > Koa > Express
(同等硬件下 Fastify QPS 约为 Express 的 2-3 倍)

常用函数详解

创建实例

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

// 等同于
const fastify = require('fastify')({
  logger: {
    level: 'info',
    transport: {
      target: 'pino-pretty',  // 本地开发美化日志
      options: { colorize: true }
    }
  }
})

// 关闭日志(生产环境)
const fastify = require('fastify')({ logger: false })

注册路由

fastify.get/post/put/delete/patch/head/options(path, options, handler)

// 最简写法
fastify.get('/hello', (req, reply) => {
  reply.send({ hello: 'world' })
})

// 带 schema 校验
fastify.get('/hello/:name', {
  schema: {
    params: {
      type: 'object',
      properties: { name: { type: 'string', minLength: 1 } }
    },
    querystring: {
      type: 'object',
      properties: {
        age: { type: 'integer', minimum: 0, maximum: 150 }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          message: { type: 'string' },
          name: { type: 'string' },
          age: { type: 'number' }
        }
      }
    }
  }
}, (req, reply) => {
  reply.send({ message: 'hello', name: req.params.name, age: req.query.age })
})

// options 可省略,handler 直接跟在路径后
fastify.post('/users', async (req, reply) => {
  return { id: 1 }  // async 函数直接 return 即响应
})

// head 请求(无响应体)
fastify.head('/resource', (req, reply) => {
  reply.header('ETag', '"abc123"').send()
})

fastify.route(options)

完整路由定义,支持更细粒度的配置。

fastify.route({
  method: ['GET', 'POST'],
  url: '/users',
  schema: { ... },
  handler: (req, reply) => { },
  onRequest: (req, reply, done) => { done() },    // 请求钩子
  preHandler: (req, reply, done) => { done() },    // 路由前置处理
  onResponse: (req, reply, done) => { done() },    // 响应后钩子
  onTimeout: (req, reply, done) => { done() },    // 超时钩子
  preSerialization: (req, reply, payload, done) => { done(null, payload) }
})

fastify.all(path, handler)

处理任意 HTTP 方法的请求。

fastify.all('/proxy', (req, reply) => {
  reply.send({ method: req.method })
})

fastify.register(plugins, opts)

注册插件,是 Fastify 的核心扩展机制。

const fp = require('fastify-plugin')
const prismaPlugin = fp(async (fastify, opts) => {
  const { PrismaClient } = require('@prisma/client')
  const prisma = new PrismaClient()
  fastify.decorate('prisma', prisma)  // 在所有子路由中可用
})

// 注册插件
fastify.register(prismaPlugin)

// 在路由中使用
fastify.get('/users', async (req, reply) => {
  return reply.fastify.prisma.user.findMany()
})

请求对象(Request)

fastify.get('/demo/:id', (req, reply) => {

  // 路径参数(自动类型转换,:id 转为 number)
  req.params.id               // '123'(字符串,默认)

  // 查询参数
  req.query.page              // '1'
  req.query.filter            // 解析后的对象

  // 请求体(需配置 bodySchema 或在路由 schema 中定义)
  req.body                    // { name: '张三' }

  // 请求头
  req.headers['authorization']
  req.headers['content-type']

  // 原始 Node.js IncomingMessage 对象
  req.raw

  // 请求 ID(每个请求唯一,用于日志追踪)
  req.id                      // '01ARH3HMS4T1F9G5J3Z...'

  // IP 相关
  req.ip                      // '::1' 或 '192.168.1.1'
  req.ips                     // ['::1', '127.0.0.1'](信任代理时)

  // 请求协议
  req.protocol                // 'http' 或 'https'

  // 请求方法
  req.method                  // 'GET'

  // 请求 URL(不含 host)
  req.url                     // '/users/123?page=1'

  // 路由对象
  req.routeOptions            // { url, method, schema, ... }

  // 上下文(注册时传入的数据)
  req.context                 // { appName, ... }
})

响应对象(Reply)

// send — 最核心方法,发送任意数据
reply.send({ data: [] })
reply.send('plain text')
reply.send(Buffer.from('binary'))

// sendFile — 发送文件(需配置 fastify-static)
reply.sendFile('report.pdf')

// sendFile 带文件名(触发下载)
reply.sendFile('report.pdf', { 
  onHeader: (res, fileName) => {
    res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`)
  }
})

// json — 发送 JSON(可省略,send 自动识别)
reply.json({ code: 0, data: [] })
reply.status(201).json({ id: 1 })   // 设置状态码 + JSON

// code / status — 设置 HTTP 状态码
reply.code(404).send({ error: 'Not Found' })
reply.status(201)

// redirect — 重定向
reply.redirect('/login')
reply.redirect(301, '/new-path')
reply.redirect('https://external.com')

// header — 设置响应头
reply.header('X-Custom', 'value')
reply.header('Content-Type', 'application/json; charset=utf-8')

// headers — 批量设置
reply.headers({
  'X-Powered-By': 'Fastify',
  'X-Request-Id': req.id
})

// type — 快捷设置 Content-Type
reply.type('application/json')

// compression
reply.sendCompressed(data)   // 发送预压缩数据

// then — Reply 支持 then(用于 async/await)
reply.send({ ok: true })
await reply  // 等待响应发送完毕

// 发送空响应
reply.code(204).send()

// 不发送内容
reply.hijack()               // 接管响应,不让 Fastify 处理

生命周期钩子

// 全局请求钩子(在所有路由前执行)
fastify.addHook('onRequest', async (req, reply) => {
  // 鉴权、IP 白名单等
})

fastify.addHook('preHandler', async (req, reply) => {
  // 权限检查、日志等
})

fastify.addHook('preSerialization', async (req, reply, payload) => {
  // 修改响应数据(如脱敏)
  return payload
})

fastify.addHook('onResponse', async (req, reply) => {
  // 记录响应时间
})

fastify.addHook('onTimeout', async (req, reply) => {
  // 请求超时处理
})

fastify.addHook('onError', async (req, reply, error) => {
  // 全局错误处理
})

// 路由级钩子(仅当前路由生效)
fastify.get('/admin', {
  onRequest: [(req, reply, done) => { done() }],
  preHandler: [(req, reply, done) => { done() }],
}, handler)

Schema 校验

Fastify 内置基于 JSON Schema 的校验,无需额外依赖。

// 请求参数校验
fastify.post('/users', {
  schema: {
    body: {                    // 请求体校验
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 2, maxLength: 50 },
        email: { type: 'string', format: 'email' },
        age: { type: 'integer', minimum: 0, maximum: 150 },
        role: { type: 'string', enum: ['admin', 'user'] },
        tags: { type: 'array', items: { type: 'string' } },
        profile: {
          type: 'object',
          properties: {
            bio: { type: 'string', maxLength: 200 }
          }
        }
      },
      additionalProperties: false  // 不允许额外字段
    },
    params: {                  // 路径参数校验
      type: 'object',
      required: ['id'],
      properties: {
        id: { type: 'string', pattern: '^[0-9]+$' }
      }
    },
    querystring: {             // 查询参数校验
      type: 'object',
      properties: {
        page: { type: 'integer', minimum: 1, default: 1 },
        limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
        sort: { type: 'string', enum: ['name', '-name', 'created'] }
      }
    },
    response: {                // 响应校验(开发辅助,生产可关闭)
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' },
          email: { type: 'string' }
        }
      }
    }
  }
}, handler)

插件(Plugin)

const fp = require('fastify-plugin')

// 普通插件
fastify.register(async (fastify, opts) => {
  fastify.get('/plugin', (req, reply) => reply.send({ plugin: true }))
})

// 封装插件(外层无法访问内部装饰器)
fastify.register(fp(async (fastify, opts) => {
  fastify.decorate('greet', (name) => `Hello, ${name}`)
}, { name: 'greeting' }))

// 在路由中使用插件内装饰器
fastify.get('/hello', (req, reply) => {
  reply.send({ message: reply.fastify.greet('World') })
})

装饰器(Decorator)

在 Fastify 实例上挂载自定义属性或方法。

// 添加属性
fastify.decorate('appName', 'MyApp')

// 添加方法
fastify.decorate('utils', {
  formatDate: (date) => date.toISOString(),
  hashPassword: (pwd) => crypto.createHash('sha256').update(pwd).digest('hex')
})

// 路由中使用
fastify.get('/date', (req, reply) => {
  reply.send({ date: fastify.utils.formatDate(new Date()) })
})

// 请求级装饰器(仅当前请求可用)
fastify.register(async (fastify) => {
  fastify.addHook('onRequest', async (req, reply) => {
    req.customData = { start: Date.now() }
  })
})

启动与关闭

// 启动服务
const start = async () => {
  try {
    await fastify.listen({ port: 3000, host: '0.0.0.0' })
    // 等同于 fastify.listen(3000)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

// 关闭服务(优雅退出)
const close = async () => {
  await fastify.close()
  process.exit(0)
}
process.on('SIGINT', close)
process.on('SIGTERM', close)

// 带 SSL 的启动
const fs = require('fs')
await fastify.listen({
  port: 3443,
  https: {
    key: fs.readFileSync('./key.pem'),
    cert: fs.readFileSync('./cert.pem')
  }
})

错误处理

// 主动抛出错误
fastify.get('/user/:id', async (req, reply) => {
  const user = await db.findUser(req.params.id)
  if (!user) {
    throw reply.notFound()           // 404
  }
  return user
})

// 错误处理钩子
fastify.setErrorHandler((error, req, reply) => {
  // validation 校验错误
  if (error.validation) {
    return reply.status(400).send({
      error: 'Validation Error',
      details: error.validation
    })
  }
  // 其他错误
  fastify.log.error(error)
  reply.status(error.statusCode || 500).send({
    error: error.message
  })
})

// 404 处理
fastify.setNotFoundHandler((req, reply) => {
  reply.status(404).send({ error: 'Route not found' })
})

常用配置

// 完整配置示例
const fastify = require('fastify')({
  logger: {
    level: 'info',
    transport: {
      target: 'pino-pretty',
      options: { colorize: true }
    }
  },
  trustProxy: true,            // 信任代理(获取真实 IP)
  disableRequestLogging: false, // 关闭自动请求日志
  requestIdHeader: 'x-request-id',  // 自定义请求 ID 头
  requestIdLogLabel: 'requestId',
  bodyLimit: 1048576,         // 请求体大小限制,默认 1MB
  onProtoPoisoning: 'remove',  // 原型污染防护
  onConstructorPoisoning: 'remove',
  ajv: {                      // 自定义 AJV 实例
    customOptions: {
      removeAdditional: true,
      coerceData: true,
      useDefaults: true
    }
  }
})

// 运行时修改配置
fastify.setContentTypeParser('application/xml', (req, done) => {
  let data = ''
  req.on('data', chunk => { data += chunk })
  req.on('end', () => done(null, data))
})

典型项目结构

src/
  app.js               // 入口
  plugins/             // 插件
    prisma.js
    auth.js
  routes/              // 路由
    users.js
    articles.js
  schemas/             // JSON Schema(可分离)
    user.js
  services/            // 业务逻辑
    userService.js
  utils/               // 工具
// app.js
const fastify = require('fastify')({ logger: true })

// 注册插件
fastify.register(require('./plugins/prisma'))
fastify.register(require('./plugins/auth'))

// 注册路由
fastify.register(require('./routes/users'), { prefix: '/api/users' })
fastify.register(require('./routes/articles'), { prefix: '/api/articles' })

// 错误处理
fastify.setErrorHandler((error, req, reply) => {
  if (error.validation) {
    return reply.status(400).send({ errors: error.validation })
  }
  reply.status(error.statusCode || 500).send({ error: error.message })
})

fastify.listen({ port: 3000 })

常见问题

问题 解决方案
请求体校验失败 400 路由缺少 schema 定义,或 schema 定义与实际请求不匹配
插件中装饰器不可用 fastify-plugin 包装插件
想在路由中用异步数据库连接 在插件 await fastify.register() 后再启动 listen
日志过多影响性能 logger: falsedisableRequestLogging: true
JSON Schema 太冗长 提取到单独文件 schemas/user.js,然后 import from './schemas/user'
跨域 fastify.register(require('@fastify/cors'), { origin: true })