数据验证 #

一、验证概述 #

Fastify内置了强大的JSON Schema验证功能,可以验证请求参数、请求体和响应数据。

1.1 验证类型 #

验证类型 Schema属性 说明
路径参数 params URL路径参数验证
查询参数 querystring URL查询参数验证
请求头 headers HTTP请求头验证
请求体 body 请求体验证
响应体 response 响应数据验证

1.2 基本验证 #

javascript
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' }
      }
    }
  }
}, async (request, reply) => {
  return { created: true }
})

二、JSON Schema基础 #

2.1 类型定义 #

javascript
fastify.post('/types', {
  schema: {
    body: {
      type: 'object',
      properties: {
        string: { type: 'string' },
        integer: { type: 'integer' },
        number: { type: 'number' },
        boolean: { type: 'boolean' },
        array: {
          type: 'array',
          items: { type: 'string' }
        },
        object: {
          type: 'object',
          properties: {
            nested: { type: 'string' }
          }
        },
        null: { type: 'null' }
      }
    }
  }
}, handler)

2.2 必需字段 #

javascript
fastify.post('/required', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string' },
        age: { type: 'integer' }
      }
    }
  }
}, handler)

2.3 字符串验证 #

javascript
fastify.post('/string', {
  schema: {
    body: {
      type: 'object',
      properties: {
        name: {
          type: 'string',
          minLength: 2,
          maxLength: 100
        },
        email: {
          type: 'string',
          format: 'email'
        },
        url: {
          type: 'string',
          format: 'uri'
        },
        date: {
          type: 'string',
          format: 'date-time'
        },
        uuid: {
          type: 'string',
          format: 'uuid'
        },
        pattern: {
          type: 'string',
          pattern: '^[A-Z]{2}\\d{4}$'
        }
      }
    }
  }
}, handler)

2.4 数字验证 #

javascript
fastify.post('/number', {
  schema: {
    body: {
      type: 'object',
      properties: {
        age: {
          type: 'integer',
          minimum: 0,
          maximum: 150
        },
        price: {
          type: 'number',
          minimum: 0,
          exclusiveMinimum: true
        },
        quantity: {
          type: 'integer',
          multipleOf: 5
        }
      }
    }
  }
}, handler)

2.5 数组验证 #

javascript
fastify.post('/array', {
  schema: {
    body: {
      type: 'object',
      properties: {
        tags: {
          type: 'array',
          items: { type: 'string' },
          minItems: 1,
          maxItems: 10,
          uniqueItems: true
        },
        numbers: {
          type: 'array',
          items: {
            type: 'integer',
            minimum: 0
          }
        },
        objects: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              id: { type: 'integer' },
              name: { type: 'string' }
            }
          }
        }
      }
    }
  }
}, handler)

2.6 枚举值 #

javascript
fastify.post('/enum', {
  schema: {
    body: {
      type: 'object',
      properties: {
        status: {
          type: 'string',
          enum: ['active', 'inactive', 'pending']
        },
        role: {
          type: 'string',
          enum: ['admin', 'user', 'guest']
        },
        priority: {
          type: 'integer',
          enum: [1, 2, 3, 4, 5]
        }
      }
    }
  }
}, handler)

三、参数验证 #

3.1 路径参数验证 #

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

fastify.get('/posts/:postId/comments/:commentId', {
  schema: {
    params: {
      type: 'object',
      properties: {
        postId: { type: 'integer' },
        commentId: { type: 'integer' }
      }
    }
  }
}, handler)

3.2 查询参数验证 #

javascript
fastify.get('/search', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        q: { type: 'string', minLength: 1 },
        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' }
      },
      required: ['q']
    }
  }
}, async (request, reply) => {
  return request.query
})

3.3 请求头验证 #

javascript
fastify.get('/protected', {
  schema: {
    headers: {
      type: 'object',
      required: ['authorization'],
      properties: {
        authorization: {
          type: 'string',
          pattern: '^Bearer .+'
        },
        'x-api-key': {
          type: 'string',
          minLength: 10
        }
      }
    }
  }
}, handler)

3.4 Cookie验证 #

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

fastify.get('/session', {
  schema: {
    cookies: {
      type: 'object',
      required: ['sessionId'],
      properties: {
        sessionId: { type: 'string', minLength: 10 }
      }
    }
  }
}, handler)

四、请求体验证 #

4.1 基本请求体验证 #

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 },
        website: { type: 'string', format: 'uri' }
      }
    }
  }
}, handler)

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 }
            }
          }
        }
      }
    }
  }
}, handler)

4.3 条件验证 #

javascript
fastify.post('/conditional', {
  schema: {
    body: {
      type: 'object',
      properties: {
        type: { type: 'string', enum: ['personal', 'business'] },
        name: { type: 'string' },
        company: { type: 'string' }
      },
      if: {
        properties: {
          type: { const: 'business' }
        }
      },
      then: {
        required: ['company']
      },
      else: {
        required: ['name']
      }
    }
  }
}, handler)

五、响应验证 #

5.1 基本响应验证 #

javascript
fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' },
          email: { type: 'string' }
        }
      },
      404: {
        type: 'object',
        properties: {
          error: { type: 'string' },
          message: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  const user = await findUser(request.params.id)
  
  if (!user) {
    reply.code(404)
    return { error: 'Not Found', message: 'User not found' }
  }
  
  return user
})

5.2 数组响应 #

javascript
fastify.get('/users', {
  schema: {
    response: {
      200: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            id: { type: 'integer' },
            name: { type: 'string' }
          }
        }
      }
    }
  }
}, async (request, reply) => {
  return [
    { id: 1, name: 'John', password: 'secret' },
    { id: 2, name: 'Jane', password: 'secret' }
  ]
})

5.3 多状态码响应 #

javascript
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' }
      }
    },
    response: {
      201: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' },
          email: { type: 'string' }
        }
      },
      400: {
        type: 'object',
        properties: {
          error: { type: 'string' },
          message: { type: 'string' }
        }
      }
    }
  }
}, handler)

六、共享Schema #

6.1 定义共享Schema #

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

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

6.2 使用共享Schema #

javascript
fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    response: {
      200: { $ref: 'user#' }
    }
  }
}, handler)

fastify.get('/users', {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          users: {
            type: 'array',
            items: { $ref: 'user#' }
          },
          pagination: { $ref: 'pagination#' }
        }
      }
    }
  }
}, handler)

七、自定义验证器 #

7.1 自定义验证编译器 #

javascript
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
  return (data) => {
    if (!data.name) {
      return { error: new Error('Name is required') }
    }
    return true
  }
})

7.2 使用Ajv自定义 #

javascript
const Ajv = require('ajv')
const ajv = new Ajv({
  allErrors: true,
  useDefaults: true,
  coerceTypes: true
})

fastify.setValidatorCompiler(({ schema }) => {
  return ajv.compile(schema)
})

7.3 自定义格式 #

javascript
const Ajv = require('ajv')
const ajv = new Ajv()

ajv.addFormat('phone', {
  type: 'string',
  validate: (value) => /^\d{11}$/.test(value)
})

fastify.setValidatorCompiler(({ schema }) => {
  return ajv.compile(schema)
})

fastify.post('/phone', {
  schema: {
    body: {
      type: 'object',
      properties: {
        phone: { type: 'string', format: 'phone' }
      }
    }
  }
}, handler)

八、错误处理 #

8.1 验证错误格式 #

json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body must have required property 'name'"
}

8.2 自定义错误消息 #

javascript
fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    reply.code(400).send({
      statusCode: 400,
      error: 'Validation Error',
      message: error.message,
      details: error.validation.map(v => ({
        field: v.instancePath || v.params.missingProperty,
        message: v.message
      }))
    })
    return
  }
  
  reply.send(error)
})

8.3 国际化错误消息 #

javascript
const errorMessages = {
  en: {
    required: 'is required',
    minLength: 'must be at least {limit} characters',
    format: 'format is invalid'
  },
  zh: {
    required: '是必需的',
    minLength: '至少需要 {limit} 个字符',
    format: '格式无效'
  }
}

fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    const lang = request.headers['accept-language'] || 'en'
    const messages = errorMessages[lang] || errorMessages.en
    
    const details = error.validation.map(v => {
      const message = messages[v.keyword] || v.message
      return {
        field: v.instancePath,
        message
      }
    })
    
    reply.code(400).send({
      statusCode: 400,
      error: 'Validation Error',
      details
    })
    return
  }
  
  reply.send(error)
})

九、最佳实践 #

9.1 Schema模块化 #

javascript
const userSchema = {
  $id: 'user',
  type: 'object',
  properties: {
    id: { type: 'integer' },
    name: { type: 'string', minLength: 2, maxLength: 100 },
    email: { type: 'string', format: 'email' }
  }
}

const userCreateSchema = {
  $id: 'userCreate',
  type: 'object',
  required: ['name', 'email'],
  properties: {
    name: { $ref: 'user#/properties/name' },
    email: { $ref: 'user#/properties/email' }
  }
}

fastify.addSchema(userSchema)
fastify.addSchema(userCreateSchema)

9.2 验证与文档同步 #

javascript
fastify.get('/users/:id', {
  schema: {
    description: 'Get user by ID',
    tags: ['Users'],
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer', description: 'User ID' }
      }
    },
    response: {
      200: {
        description: 'User found',
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' }
        }
      }
    }
  }
}, handler)

十、总结 #

本章我们学习了:

  1. 验证类型:params、querystring、headers、body、response
  2. JSON Schema基础:类型、必需字段、字符串、数字、数组、枚举
  3. 参数验证:路径参数、查询参数、请求头、Cookie
  4. 请求体验证:基本验证、嵌套对象、条件验证
  5. 响应验证:基本响应、数组响应、多状态码
  6. 共享Schema:定义和使用共享Schema
  7. 自定义验证器:验证编译器、自定义格式
  8. 错误处理:错误格式、自定义消息、国际化
  9. 最佳实践:Schema模块化、验证与文档同步

接下来让我们学习序列化响应!

最后更新:2026-03-28