路由参数 #

一、参数类型概述 #

Fastify支持多种参数类型,每种都有不同的获取方式。

1.1 参数分类 #

参数类型 位置 获取方式 示例
路径参数 URL路径 request.params /users/:id
查询参数 URL查询字符串 request.query ?page=1&limit=10
请求体 请求体 request.body POST数据
请求头 HTTP头部 request.headers Authorization
Cookie Cookie头部 request.cookies sessionId

1.2 参数获取示例 #

javascript
fastify.post('/users/:id/profile', async (request, reply) => {
  const { id } = request.params
  const { fields } = request.query
  const body = request.body
  const token = request.headers.authorization
  const sessionId = request.cookies.sessionId
  
  return { id, fields, body, token, sessionId }
})

二、路径参数 #

2.1 基本用法 #

javascript
fastify.get('/users/:id', async (request, reply) => {
  return { userId: request.params.id }
})

fastify.get('/posts/:postId/comments/:commentId', async (request, reply) => {
  const { postId, commentId } = request.params
  return { postId, commentId }
})

2.2 参数验证 #

javascript
fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer', minimum: 1 }
      },
      required: ['id']
    }
  }
}, async (request, reply) => {
  return { userId: request.params.id }
})

2.3 正则验证 #

javascript
fastify.get('/users/:id(\\d+)', async (request, reply) => {
  return { userId: parseInt(request.params.id) }
})

fastify.get('/files/:filename(.*\\.txt)', async (request, reply) => {
  return { filename: request.params.filename }
})

fastify.get('/date/:date(\\d{4}-\\d{2}-\\d{2})', async (request, reply) => {
  return { date: request.params.date }
})

2.4 可选参数 #

javascript
fastify.get('/users/:id?', async (request, reply) => {
  if (request.params.id) {
    return { userId: request.params.id }
  }
  return { users: 'all' }
})

2.5 通配符参数 #

javascript
fastify.get('/files/*', async (request, reply) => {
  return { path: request.params['*'] }
})

fastify.get('/static/**', async (request, reply) => {
  return { path: request.params['**'] }
})

三、查询参数 #

3.1 基本用法 #

javascript
fastify.get('/search', async (request, reply) => {
  const { q, page, limit } = request.query
  return { query: q, page, limit }
})

3.2 参数验证 #

javascript
fastify.get('/users', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        page: { type: 'integer', minimum: 1, default: 1 },
        limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
        sort: { type: 'string', enum: ['name', 'date', 'id'] },
        order: { type: 'string', enum: ['asc', 'desc'], default: 'asc' },
        search: { type: 'string', minLength: 1 }
      }
    }
  }
}, async (request, reply) => {
  const { page, limit, sort, order, search } = request.query
  return { page, limit, sort, order, search }
})

3.3 数组参数 #

javascript
fastify.get('/users', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        ids: {
          type: 'array',
          items: { type: 'integer' }
        }
      }
    }
  }
}, async (request, reply) => {
  return { ids: request.query.ids }
})

请求:/users?ids=1&ids=2&ids=3

3.4 嵌套对象参数 #

javascript
fastify.get('/search', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        filter: {
          type: 'object',
          properties: {
            status: { type: 'string' },
            category: { type: 'string' }
          }
        }
      }
    }
  }
}, async (request, reply) => {
  return { filter: request.query.filter }
})

请求:/search?filter[status]=active&filter[category]=tech

3.5 自定义解析器 #

javascript
fastify.get('/search', async (request, reply) => {
  const parsed = {}
  
  for (const [key, value] of Object.entries(request.query)) {
    if (key.startsWith('filter.')) {
      const filterKey = key.replace('filter.', '')
      parsed[filterKey] = value
    }
  }
  
  return { filters: parsed }
})

四、请求体 #

4.1 JSON请求体 #

javascript
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 2, maxLength: 100 },
        email: { type: 'string', format: 'email' },
        age: { type: 'integer', minimum: 0, maximum: 150 },
        tags: {
          type: 'array',
          items: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  return { created: request.body }
})

4.2 嵌套对象 #

javascript
fastify.post('/orders', {
  schema: {
    body: {
      type: 'object',
      required: ['customer', 'items'],
      properties: {
        customer: {
          type: 'object',
          required: ['name', 'email'],
          properties: {
            name: { type: 'string' },
            email: { type: 'string', format: 'email' },
            address: {
              type: 'object',
              properties: {
                street: { type: 'string' },
                city: { type: 'string' },
                zipCode: { type: 'string' }
              }
            }
          }
        },
        items: {
          type: 'array',
          minItems: 1,
          items: {
            type: 'object',
            required: ['productId', 'quantity'],
            properties: {
              productId: { type: 'integer' },
              quantity: { type: 'integer', minimum: 1 },
              price: { type: 'number', minimum: 0 }
            }
          }
        }
      }
    }
  }
}, async (request, reply) => {
  return { order: request.body }
})

4.3 表单数据 #

javascript
fastify.post('/login', async (request, reply) => {
  const { username, password } = request.body
  return { username }
})

4.4 文件上传 #

javascript
fastify.register(require('@fastify/multipart'))

fastify.post('/upload', async (request, reply) => {
  const data = await request.file()
  
  return {
    filename: data.filename,
    mimetype: data.mimetype,
    encoding: data.encoding
  }
})

4.5 多文件上传 #

javascript
fastify.post('/uploads', async (request, reply) => {
  const files = []
  
  for await (const part of request.parts()) {
    if (part.type === 'file') {
      files.push({
        filename: part.filename,
        mimetype: part.mimetype
      })
    }
  }
  
  return { files }
})

五、请求头 #

5.1 获取请求头 #

javascript
fastify.get('/headers', async (request, reply) => {
  return {
    userAgent: request.headers['user-agent'],
    authorization: request.headers.authorization,
    contentType: request.headers['content-type'],
    accept: request.headers.accept,
    allHeaders: request.headers
  }
})

5.2 请求头验证 #

javascript
fastify.get('/protected', {
  schema: {
    headers: {
      type: 'object',
      required: ['authorization'],
      properties: {
        authorization: { type: 'string', pattern: '^Bearer .+' }
      }
    }
  }
}, async (request, reply) => {
  const token = request.headers.authorization.replace('Bearer ', '')
  return { token }
})

5.3 自定义请求头 #

javascript
fastify.get('/api', {
  schema: {
    headers: {
      type: 'object',
      properties: {
        'x-api-key': { type: 'string' },
        'x-request-id': { type: 'string', format: 'uuid' },
        'x-api-version': { type: 'string', enum: ['v1', 'v2'] }
      }
    }
  }
}, async (request, reply) => {
  return {
    apiKey: request.headers['x-api-key'],
    requestId: request.headers['x-request-id'],
    version: request.headers['x-api-version']
  }
})

六、Cookie #

6.1 安装Cookie插件 #

javascript
fastify.register(require('@fastify/cookie'))

6.2 获取Cookie #

javascript
fastify.get('/get-cookies', async (request, reply) => {
  return {
    all: request.cookies,
    sessionId: request.cookies.sessionId,
    userId: request.cookies.userId
  }
})

6.3 设置Cookie #

javascript
fastify.get('/set-cookie', async (request, reply) => {
  reply.setCookie('sessionId', 'abc123', {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 3600,
    path: '/'
  })
  
  return { message: 'Cookie set' }
})

6.4 Cookie验证 #

javascript
fastify.get('/protected', {
  schema: {
    cookies: {
      type: 'object',
      required: ['sessionId'],
      properties: {
        sessionId: { type: 'string', minLength: 10 }
      }
    }
  }
}, async (request, reply) => {
  return { sessionId: request.cookies.sessionId }
})

七、参数预处理 #

7.1 使用preHandler #

javascript
fastify.get('/users/:id', {
  preHandler: async (request, reply) => {
    request.params.id = parseInt(request.params.id)
  }
}, async (request, reply) => {
  return { userId: request.params.id }
})

7.2 参数转换 #

javascript
fastify.get('/search', {
  preHandler: async (request, reply) => {
    if (request.query.page) {
      request.query.page = parseInt(request.query.page)
    }
    if (request.query.limit) {
      request.query.limit = parseInt(request.query.limit)
    }
  }
}, async (request, reply) => {
  return request.query
})

7.3 参数清理 #

javascript
fastify.post('/users', {
  preHandler: async (request, reply) => {
    if (request.body.name) {
      request.body.name = request.body.name.trim()
    }
    if (request.body.email) {
      request.body.email = request.body.email.toLowerCase()
    }
  }
}, async (request, reply) => {
  return request.body
})

八、共享Schema #

8.1 定义共享Schema #

javascript
fastify.addSchema({
  $id: 'userId',
  type: 'integer',
  minimum: 1
})

fastify.addSchema({
  $id: 'pagination',
  type: 'object',
  properties: {
    page: { type: 'integer', minimum: 1, default: 1 },
    limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }
  }
})

8.2 使用共享Schema #

javascript
fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { $ref: 'userId#' }
      }
    }
  }
}, async (request, reply) => {
  return { userId: request.params.id }
})

fastify.get('/posts', {
  schema: {
    querystring: { $ref: 'pagination#' }
  }
}, async (request, reply) => {
  return request.query
})

九、错误响应 #

9.1 验证错误格式 #

当参数验证失败时,Fastify返回标准错误格式:

json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "params/id must be integer"
}

9.2 自定义错误消息 #

javascript
fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer', errorMessage: 'ID必须是整数' }
      }
    }
  }
}, async (request, reply) => {
  return { userId: request.params.id }
})

9.3 自定义错误处理器 #

javascript
fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    reply.code(400).send({
      statusCode: 400,
      error: 'Validation Error',
      message: error.message,
      details: error.validation
    })
    return
  }
  
  reply.send(error)
})

十、完整示例 #

10.1 用户API #

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

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

let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
]

fastify.get('/users', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        page: { type: 'integer', minimum: 1, default: 1 },
        limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
        search: { type: 'string' }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          users: {
            type: 'array',
            items: { $ref: 'user#' }
          },
          pagination: {
            type: 'object',
            properties: {
              page: { type: 'integer' },
              limit: { type: 'integer' },
              total: { type: 'integer' }
            }
          }
        }
      }
    }
  }
}, async (request, reply) => {
  const { page, limit, search } = request.query
  
  let filtered = users
  if (search) {
    filtered = users.filter(u => 
      u.name.includes(search) || u.email.includes(search)
    )
  }
  
  return {
    users: filtered.slice((page - 1) * limit, page * limit),
    pagination: {
      page,
      limit,
      total: filtered.length
    }
  }
})

fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 2 },
        email: { type: 'string', format: 'email' }
      }
    },
    response: {
      201: { $ref: 'user#' }
    }
  }
}, async (request, reply) => {
  const user = {
    id: users.length + 1,
    ...request.body
  }
  users.push(user)
  
  reply.code(201)
  return user
})

fastify.listen({ port: 3000 })

十一、总结 #

本章我们学习了:

  1. 参数类型:路径参数、查询参数、请求体、请求头、Cookie
  2. 路径参数:基本用法、验证、正则、通配符
  3. 查询参数:基本用法、验证、数组、嵌套对象
  4. 请求体:JSON、表单、文件上传
  5. 请求头:获取、验证、自定义头
  6. Cookie:获取、设置、验证
  7. 参数预处理:preHandler、转换、清理
  8. 共享Schema:定义、使用
  9. 错误处理:自定义错误消息、错误处理器

接下来让我们学习路由钩子!

最后更新:2026-03-28