路由方法 #

一、HTTP方法概述 #

HTTP方法定义了对资源的操作类型,Fastify支持所有标准HTTP方法。

1.1 方法分类 #

类型 方法 说明
安全方法 GET, HEAD, OPTIONS, TRACE 不修改资源状态
幂等方法 GET, HEAD, OPTIONS, TRACE, PUT, DELETE 多次执行结果相同
非幂等方法 POST, PATCH 多次执行可能产生不同结果

1.2 方法注册 #

javascript
const fastify = require('fastify')()

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

二、GET方法 #

2.1 基本用法 #

GET方法用于获取资源,应该是安全且幂等的。

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

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

2.2 带查询参数 #

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

2.3 条件请求 #

javascript
fastify.get('/users/:id', async (request, reply) => {
  const user = { id: request.params.id, name: 'John', version: 2 }
  const etag = `W/"${user.version}"`
  
  if (request.headers['if-none-match'] === etag) {
    reply.code(304).send()
    return
  }
  
  reply.header('ETag', etag)
  return user
})

2.4 缓存控制 #

javascript
fastify.get('/static-data', async (request, reply) => {
  reply
    .header('Cache-Control', 'public, max-age=3600')
    .header('Last-Modified', new Date().toUTCString())
  
  return { data: 'cached content' }
})

三、POST方法 #

3.1 基本用法 #

POST方法用于创建资源,不是幂等的。

javascript
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 2 },
        email: { type: 'string', format: 'email' },
        age: { type: 'integer', minimum: 0 }
      }
    }
  }
}, async (request, reply) => {
  const user = {
    id: Date.now(),
    ...request.body,
    createdAt: new Date()
  }
  
  reply.code(201)
  reply.header('Location', `/users/${user.id}`)
  return user
})

3.2 表单数据处理 #

javascript
fastify.post('/login', {
  schema: {
    body: {
      type: 'object',
      required: ['username', 'password'],
      properties: {
        username: { type: 'string' },
        password: { type: 'string', minLength: 6 }
      }
    }
  }
}, async (request, reply) => {
  const { username, password } = request.body
  
  if (username === 'admin' && password === 'password') {
    return { token: 'jwt-token-here' }
  }
  
  reply.code(401)
  return { error: 'Invalid credentials' }
})

3.3 批量创建 #

javascript
fastify.post('/users/batch', {
  schema: {
    body: {
      type: 'object',
      required: ['users'],
      properties: {
        users: {
          type: 'array',
          minItems: 1,
          maxItems: 100,
          items: {
            type: 'object',
            required: ['name', 'email'],
            properties: {
              name: { type: 'string' },
              email: { type: 'string', format: 'email' }
            }
          }
        }
      }
    }
  }
}, async (request, reply) => {
  const created = request.body.users.map((user, index) => ({
    id: index + 1,
    ...user
  }))
  
  reply.code(201)
  return { created, count: created.length }
})

四、PUT方法 #

4.1 基本用法 #

PUT方法用于完整更新资源,是幂等的。

javascript
fastify.put('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' },
        age: { type: 'integer' }
      }
    }
  }
}, async (request, reply) => {
  const { id } = request.params
  
  return {
    id,
    ...request.body,
    updatedAt: new Date()
  }
})

4.2 创建或更新 #

javascript
fastify.put('/users/:id', async (request, reply) => {
  const { id } = request.params
  const exists = await checkUserExists(id)
  
  if (exists) {
    reply.code(200)
    return { action: 'updated', id }
  }
  
  reply.code(201)
  return { action: 'created', id }
})

五、PATCH方法 #

5.1 基本用法 #

PATCH方法用于部分更新资源,不是幂等的。

javascript
fastify.patch('/users/:id', {
  schema: {
    body: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' },
        age: { type: 'integer' }
      }
    }
  }
}, async (request, reply) => {
  const { id } = request.params
  const updates = request.body
  
  if (Object.keys(updates).length === 0) {
    reply.code(400)
    return { error: 'No fields to update' }
  }
  
  return {
    id,
    ...updates,
    updatedAt: new Date()
  }
})

5.2 JSON Patch支持 #

javascript
fastify.patch('/users/:id', {
  schema: {
    body: {
      type: 'array',
      items: {
        type: 'object',
        required: ['op', 'path'],
        properties: {
          op: { type: 'string', enum: ['add', 'remove', 'replace', 'move', 'copy', 'test'] },
          path: { type: 'string' },
          value: {}
        }
      }
    }
  }
}, async (request, reply) => {
  const patches = request.body
  
  return {
    message: 'Patches applied',
    patches
  }
})

六、DELETE方法 #

6.1 基本用法 #

DELETE方法用于删除资源,是幂等的。

javascript
fastify.delete('/users/:id', async (request, reply) => {
  const { id } = request.params
  
  reply.code(204).send()
})

6.2 返回删除的资源 #

javascript
fastify.delete('/users/:id', async (request, reply) => {
  const { id } = request.params
  const user = { id, name: 'Deleted User' }
  
  return { deleted: user }
})

6.3 批量删除 #

javascript
fastify.delete('/users', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        ids: { type: 'string' }
      }
    }
  }
}, async (request, reply) => {
  const ids = request.query.ids.split(',').map(Number)
  
  return {
    deleted: ids.length,
    ids
  }
})

七、HEAD方法 #

7.1 基本用法 #

HEAD方法与GET类似,但只返回响应头。

javascript
fastify.head('/users/:id', async (request, reply) => {
  const exists = await checkUserExists(request.params.id)
  
  if (!exists) {
    reply.code(404).send()
    return
  }
  
  reply
    .header('Content-Type', 'application/json')
    .header('Content-Length', '100')
    .code(200)
    .send()
})

7.2 检查资源状态 #

javascript
fastify.head('/files/:filename', async (request, reply) => {
  const stats = await getFileStats(request.params.filename)
  
  if (!stats) {
    reply.code(404).send()
    return
  }
  
  reply
    .header('Content-Length', stats.size)
    .header('Last-Modified', stats.mtime.toUTCString())
    .header('Content-Type', stats.mimeType)
    .code(200)
    .send()
})

八、OPTIONS方法 #

8.1 基本用法 #

OPTIONS方法用于获取资源支持的方法。

javascript
fastify.options('/users', async (request, reply) => {
  reply
    .header('Allow', 'GET, POST, PUT, DELETE, OPTIONS')
    .header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
    .header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
    .code(204)
    .send()
})

8.2 CORS预检 #

javascript
fastify.options('/users/:id', async (request, reply) => {
  reply
    .header('Access-Control-Allow-Origin', '*')
    .header('Access-Control-Allow-Methods', 'GET, PUT, DELETE')
    .header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
    .header('Access-Control-Max-Age', '86400')
    .code(204)
    .send()
})

九、ALL方法 #

9.1 匹配所有方法 #

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

9.2 通用处理 #

javascript
fastify.all('/legacy/*', async (request, reply) => {
  reply.code(410)
  return {
    error: 'This endpoint is deprecated',
    migration: '/api/v2' + request.params['*']
  }
})

十、自定义方法 #

10.1 注册自定义方法 #

javascript
fastify.route({
  method: 'REPORT',
  url: '/reports',
  handler: async (request, reply) => {
    return { report: 'generated' }
  }
})

10.2 WebDAV方法 #

javascript
fastify.route({
  method: 'PROPFIND',
  url: '/dav/*',
  handler: async (request, reply) => {
    reply.header('Content-Type', 'application/xml')
    return `<?xml version="1.0"?><response/>`
  }
})

fastify.route({
  method: 'COPY',
  url: '/dav/*',
  handler: async (request, reply) => {
    return { copied: true }
  }
})

十一、方法覆盖 #

11.1 X-HTTP-Method-Override #

javascript
fastify.addHook('preHandler', async (request, reply) => {
  const override = request.headers['x-http-method-override']
  if (override) {
    request.method = override.toUpperCase()
  }
})

fastify.post('/users', async (request, reply) => {
  if (request.method === 'PUT') {
    return { action: 'update' }
  }
  return { action: 'create' }
})

11.2 _method参数 #

javascript
fastify.addHook('preHandler', async (request, reply) => {
  if (request.method === 'POST' && request.body._method) {
    request.method = request.body._method.toUpperCase()
    delete request.body._method
  }
})

十二、方法最佳实践 #

12.1 RESTful API设计 #

javascript
const fastify = require('fastify')()

let users = []

fastify.get('/users', async (request, reply) => {
  return { users }
})

fastify.post('/users', async (request, reply) => {
  const user = { id: users.length + 1, ...request.body }
  users.push(user)
  reply.code(201)
  return user
})

fastify.get('/users/:id', async (request, reply) => {
  const user = users.find(u => u.id === parseInt(request.params.id))
  if (!user) {
    reply.code(404)
    return { error: 'User not found' }
  }
  return user
})

fastify.put('/users/:id', async (request, reply) => {
  const index = users.findIndex(u => u.id === parseInt(request.params.id))
  if (index === -1) {
    reply.code(404)
    return { error: 'User not found' }
  }
  users[index] = { id: parseInt(request.params.id), ...request.body }
  return users[index]
})

fastify.patch('/users/:id', async (request, reply) => {
  const user = users.find(u => u.id === parseInt(request.params.id))
  if (!user) {
    reply.code(404)
    return { error: 'User not found' }
  }
  Object.assign(user, request.body)
  return user
})

fastify.delete('/users/:id', async (request, reply) => {
  const index = users.findIndex(u => u.id === parseInt(request.params.id))
  if (index === -1) {
    reply.code(404)
    return { error: 'User not found' }
  }
  users.splice(index, 1)
  reply.code(204).send()
})

12.2 状态码规范 #

方法 成功状态码 说明
GET 200 成功返回数据
POST 201 创建成功
PUT 200 更新成功
PATCH 200 部分更新成功
DELETE 204 删除成功(无内容)

十三、总结 #

本章我们学习了:

  1. GET方法:获取资源、查询参数、缓存控制
  2. POST方法:创建资源、表单处理、批量创建
  3. PUT方法:完整更新、创建或更新
  4. PATCH方法:部分更新、JSON Patch
  5. DELETE方法:删除资源、批量删除
  6. HEAD方法:获取响应头
  7. OPTIONS方法:获取支持的方法、CORS预检
  8. ALL方法:匹配所有方法
  9. 自定义方法:WebDAV等扩展方法
  10. 最佳实践:RESTful设计、状态码规范

接下来让我们学习路由参数!

最后更新:2026-03-28