SuperAPI AI 工具集

fastify-v5

编程与开发

查看项目

更新于 10-27 16:21

技能介绍

用途

fastify-v5 技能用于指导在 Fastify v5+ 中结合 TypeBox schema 进行类型安全的 API 开发。它强调使用原生 JSON Schema 进行路由校验,并支持自动生成 OpenAPI 文档。适用于构建 Fastify REST API 的场景。

该技能明确不覆盖数据库集成,相关需求建议参考 drizzle-orm。

前提假设

  • Fastify v5+
  • @fastify/type-provider-typebox 作为 TypeBox 类型提供器
  • @sinclair/typebox 用于 schema 定义
  • TypeScript v5+ 且开启 strict 模式

主要能力与原则

  • 使用 TypeBox schema 对路由进行校验(params、query、body、response)
  • 使用 TypeBoxTypeProvider 从 schema 自动推断类型
  • 为所有状态码定义响应 schema
  • 通过 @fastify/swagger 从 schema 生成 OpenAPI
  • request.params、request.query、request.body 的类型由 schema 推断
  • 响应校验基于 schema 自动进行

约束

必须(MUST):

  • 使用 TypeBoxTypeProvider 保证类型安全:fastify.withTypeProvider<TypeBoxTypeProvider>()
  • 为所有状态码定义响应 schema
  • 使用 TypeBox 的 Type.* 构造器定义 schema

建议(SHOULD):

  • 在路由 schema 中使用 operationId 以便生成 OpenAPI
  • 使用 tags 组织 OpenAPI 文档中的路由
  • 为错误情况定义错误响应 schema

避免(AVOID):

  • 手动类型断言(类型应从 schema 推断)
  • 跳过响应 schema 定义
  • 在同一路由中混用 TypeBox 与其他 schema 库

交互

  • 与 drizzle-orm 互补,用于数据库集成
  • 生成的 OpenAPI 规范兼容 openapi-ts 代码生成

使用方式

路由 Schema 模式

import { Type } from '@sinclair/typebox'
import type { FastifyPluginAsync } from 'fastify'

const UserSchema = Type.Object({
  id: Type.String(),
  email: Type.String({ format: 'email' }),
  name: Type.String(),
})

const userRoutes: FastifyPluginAsync = async fastify => {
  fastify.get('/users/:id', {
    schema: {
      operationId: 'getUser',
      params: Type.Object({ id: Type.String() }),
      response: {
        200: UserSchema,
        404: Type.Object({ code: Type.String(), message: Type.String() }),
      },
    },
  }, async (request, reply) => {
    // request.params.id is typed
    const { id } = request.params
    return reply.send(user)
  })
}

Schema 类型

  • Type.String()
  • Type.Number()
  • Type.Boolean()
  • Type.Object({ id: Type.String() })
  • Type.Array(Type.String())
  • Type.Optional(Type.String())
  • Type.Union([Type.String(), Type.Number()])

实例配置

import type { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import Fastify, { LogController } from 'fastify'

const fastify = Fastify({
  logger: {
    level: 'info',
    transport: process.env.NODE_ENV === 'development' ? {
      target: 'pino-pretty',
    } : undefined,
  },
  trustProxy: true,
  bodyLimit: 1048576, // 1MB
  requestTimeout: 30000,
  requestIdHeader: 'x-request-id',
  logController: new LogController({ requestIdLogLabel: 'reqId', disableRequestLogging: false }),
}).withTypeProvider<TypeBoxTypeProvider>()

插件开发

使用 fastify-plugin 创建可复用的非封装插件:

import type { FastifyPluginAsync } from 'fastify'
import fp from 'fastify-plugin'

const myPlugin: FastifyPluginAsync = async fastify => {
  // Plugin logic
}

export default fp(myPlugin, {
  name: 'my-plugin',
})

钩子(请求生命周期)

fastify.addHook('onRequest', async (request, reply) => {
  // Add security headers, logging, etc.
})

fastify.addHook('onResponse', async (request, reply) => {
  // Modify response, add headers, etc.
})

fastify.addHook('onError', async (request, reply, error) => {
  // Handle errors
})

使用 inject() 测试

const response = await fastify.inject({
  method: 'GET',
  url: '/users/123',
})

expect(response.statusCode).toBe(200)
const data = JSON.parse(response.body)

流式响应

fastify.post('/stream', {
  schema: {
    response: {
      200: Type.String({ description: 'Streaming response' }),
    },
  },
}, async (request, reply) => {
  reply.header('Content-Type', 'text/event-stream')
  reply.header('Cache-Control', 'no-cache')
  return reply.send(stream)
})

常见插件

限流:

import rateLimit from '@fastify/rate-limit'

await fastify.register(rateLimit, {
  max: 100,
  timeWindow: 60000,
  keyGenerator: request => request.ip,
})

CORS:

import cors from '@fastify/cors'

await fastify.register(cors, {
  origin: (origin, callback) => {
    // Validate origin
    callback(null, true)
  },
  credentials: false,
})

错误处理:

fastify.setErrorHandler((error, request, reply) => {
  // Global error handling
  reply.status(error.statusCode ?? 500).send({
    code: 'ERROR',
    message: error.message,
  })
})

优雅关闭:

const shutdown = async (signal: string) => {
  fastify.log.info({ signal }, 'Shutting down')
  await fastify.close()
  process.exit(0)
}

process.on('SIGTERM', () => shutdown('SIGTERM'))
process.on('SIGINT', () => shutdown('SIGINT'))

AutoLoad 模式

使用 @fastify/autoload 自动发现插件与路由:

import AutoLoad from '@fastify/autoload'

await fastify.register(AutoLoad, {
  dir: path.join(__dirname, 'plugins'),
  forceESM: true,
})

适用场景

  • 构建 Fastify v5+ REST API
  • 需要类型安全的路由定义与请求/响应类型推断
  • 需要从 schema 自动生成 OpenAPI 文档
  • 需要插件化架构、请求生命周期钩子、inject() 测试、流式响应
  • 需要限流、CORS、错误处理、优雅关闭等常见能力

限制

  • 不覆盖数据库集成(建议参考 drizzle-orm)
  • 依赖 Fastify v5+、TypeScript v5+ strict 模式及上述 TypeBox 相关包
  • 同一路由中不应混用 TypeBox 与其他 schema 库
  • 不应跳过响应 schema 定义或使用手动类型断言

参考资料

  • OpenAPI Integration - OpenAPI 生成模式
  • Plugin Patterns - 使用 fastify-plugin 开发插件
  • Hooks Guide - 请求生命周期钩子
  • Testing Patterns - 使用 inject() 测试
  • Instance Configuration - Fastify 实例配置