hono-validation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHono Validation Patterns
Hono 请求验证模式
Overview
概述
Hono provides a lightweight built-in validator and integrates seamlessly with popular validation libraries like Zod, TypeBox, and Valibot. Validation happens as middleware, providing type-safe access to validated data in handlers.
Key Features:
- Built-in lightweight validator
- First-class Zod integration via
@hono/zod-validator - Standard Schema support (works with any validation library)
- Type inference from validation schemas
- Validates: JSON, forms, query params, headers, cookies, path params
Hono 提供轻量级内置验证器,并与Zod、TypeBox和Valibot等热门验证库无缝集成。验证通过中间件实现,允许在处理程序中类型安全地访问已验证的数据。
主要特性:
- 内置轻量级验证器
- 通过提供一流的Zod集成
@hono/zod-validator - 标准Schema支持(可与任何验证库配合使用)
- 从验证Schema推断类型
- 可验证:JSON、表单、查询参数、标头、Cookie、路径参数
When to Use This Skill
何时使用该技能
Use Hono validation when:
- Validating API request bodies (JSON, form data)
- Ensuring query parameters meet requirements
- Validating authentication headers
- Type-safe path parameter parsing
- Cookie validation
在以下场景使用Hono验证:
- 验证API请求体(JSON、表单数据)
- 确保查询参数符合要求
- 验证身份验证标头
- 类型安全的路径参数解析
- Cookie验证
Installation
安装
bash
undefinedbash
undefinedZod (recommended)
Zod(推荐)
npm install @hono/zod-validator zod
npm install @hono/zod-validator zod
TypeBox
TypeBox
npm install @hono/typebox-validator @sinclair/typebox
npm install @hono/typebox-validator @sinclair/typebox
Valibot
Valibot
npm install @hono/valibot-validator valibot
npm install @hono/valibot-validator valibot
Standard Schema (any compatible library)
标准Schema(兼容任何库)
npm install @hono/standard-validator
undefinednpm install @hono/standard-validator
undefinedZod Validation (Recommended)
Zod验证(推荐)
Basic Usage
基础用法
typescript
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
// Define schema
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional()
})
// Apply validation
app.post(
'/users',
zValidator('json', createUserSchema),
(c) => {
// Fully typed! { name: string; email: string; age?: number }
const data = c.req.valid('json')
return c.json({ user: data }, 201)
}
)typescript
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
// 定义Schema
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional()
})
// 应用验证
app.post(
'/users',
zValidator('json', createUserSchema),
(c) => {
// 完全类型化!{ name: string; email: string; age?: number }
const data = c.req.valid('json')
return c.json({ user: data }, 201)
}
)Validation Targets
验证目标
typescript
// JSON body
app.post('/api', zValidator('json', schema), handler)
// Form data (multipart or urlencoded)
app.post('/form', zValidator('form', schema), handler)
// Query parameters
app.get('/search', zValidator('query', z.object({
q: z.string(),
page: z.coerce.number().default(1),
limit: z.coerce.number().max(100).default(20)
})), handler)
// Path parameters
app.get('/users/:id', zValidator('param', z.object({
id: z.string().uuid()
})), handler)
// Headers (use lowercase!)
app.post('/api', zValidator('header', z.object({
'authorization': z.string().startsWith('Bearer '),
'x-request-id': z.string().uuid().optional()
})), handler)
// Cookies
app.get('/dashboard', zValidator('cookie', z.object({
session: z.string().min(1)
})), handler)typescript
// JSON 体
app.post('/api', zValidator('json', schema), handler)
// 表单数据(multipart或urlencoded)
app.post('/form', zValidator('form', schema), handler)
// 查询参数
app.get('/search', zValidator('query', z.object({
q: z.string(),
page: z.coerce.number().default(1),
limit: z.coerce.number().max(100).default(20)
})), handler)
// 路径参数
app.get('/users/:id', zValidator('param', z.object({
id: z.string().uuid()
})), handler)
// 标头(使用小写!)
app.post('/api', zValidator('header', z.object({
'authorization': z.string().startsWith('Bearer '),
'x-request-id': z.string().uuid().optional()
})), handler)
// Cookies
app.get('/dashboard', zValidator('cookie', z.object({
session: z.string().min(1)
})), handler)Custom Error Handling
自定义错误处理
typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// Custom error response
app.post(
'/users',
zValidator('json', createUserSchema, (result, c) => {
if (!result.success) {
return c.json({
error: 'Validation failed',
details: result.error.flatten()
}, 400)
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ user: data }, 201)
}
)typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// 自定义错误响应
app.post(
'/users',
zValidator('json', createUserSchema, (result, c) => {
if (!result.success) {
return c.json({
error: '验证失败',
details: result.error.flatten()
}, 400)
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ user: data }, 201)
}
)Multiple Validators
多验证器
typescript
const paramsSchema = z.object({
userId: z.string().uuid()
})
const bodySchema = z.object({
name: z.string().optional(),
email: z.string().email().optional()
})
const querySchema = z.object({
fields: z.string().optional()
})
app.patch(
'/users/:userId',
zValidator('param', paramsSchema),
zValidator('json', bodySchema),
zValidator('query', querySchema),
(c) => {
const { userId } = c.req.valid('param')
const body = c.req.valid('json')
const { fields } = c.req.valid('query')
return c.json({ updated: { userId, ...body } })
}
)typescript
const paramsSchema = z.object({
userId: z.string().uuid()
})
const bodySchema = z.object({
name: z.string().optional(),
email: z.string().email().optional()
})
const querySchema = z.object({
fields: z.string().optional()
})
app.patch(
'/users/:userId',
zValidator('param', paramsSchema),
zValidator('json', bodySchema),
zValidator('query', querySchema),
(c) => {
const { userId } = c.req.valid('param')
const body = c.req.valid('json')
const { fields } = c.req.valid('query')
return c.json({ updated: { userId, ...body } })
}
)Common Zod Patterns
常见Zod模式
Coercion for Query/Form Data
转换查询/表单数据
typescript
// Query params come as strings - use coerce
const paginationSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['asc', 'desc']).default('desc')
})
app.get('/items', zValidator('query', paginationSchema), (c) => {
const { page, limit, sort } = c.req.valid('query')
// page: number, limit: number, sort: 'asc' | 'desc'
})typescript
// 查询参数以字符串形式传递 - 使用coerce
const paginationSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['asc', 'desc']).default('desc')
})
app.get('/items', zValidator('query', paginationSchema), (c) => {
const { page, limit, sort } = c.req.valid('query')
// page: number, limit: number, sort: 'asc' | 'desc'
})Optional with Defaults
可选字段与默认值
typescript
const configSchema = z.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true),
language: z.string().default('en')
})typescript
const configSchema = z.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true),
language: z.string().default('en')
})Transformations
数据转换
typescript
const userSchema = z.object({
email: z.string().email().toLowerCase(),
name: z.string().trim(),
tags: z.string().transform(s => s.split(',')), // "a,b,c" → ["a","b","c"]
createdAt: z.string().transform(s => new Date(s))
})typescript
const userSchema = z.object({
email: z.string().email().toLowerCase(),
name: z.string().trim(),
tags: z.string().transform(s => s.split(',')), // "a,b,c" → ["a","b","c"]
createdAt: z.string().transform(s => new Date(s))
})Refinements
自定义验证
typescript
const passwordSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword']
})
const dateRangeSchema = z.object({
startDate: z.coerce.date(),
endDate: z.coerce.date()
}).refine(data => data.endDate > data.startDate, {
message: 'End date must be after start date'
})typescript
const passwordSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
message: "两次输入的密码不一致",
path: ['confirmPassword']
})
const dateRangeSchema = z.object({
startDate: z.coerce.date(),
endDate: z.coerce.date()
}).refine(data => data.endDate > data.startDate, {
message: '结束日期必须晚于开始日期'
})Discriminated Unions
区分联合类型
typescript
const eventSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('click'),
x: z.number(),
y: z.number()
}),
z.object({
type: z.literal('scroll'),
direction: z.enum(['up', 'down'])
}),
z.object({
type: z.literal('keypress'),
key: z.string()
})
])
app.post('/events', zValidator('json', eventSchema), (c) => {
const event = c.req.valid('json')
if (event.type === 'click') {
console.log(event.x, event.y) // Typed correctly!
}
})typescript
const eventSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('click'),
x: z.number(),
y: z.number()
}),
z.object({
type: z.literal('scroll'),
direction: z.enum(['up', 'down'])
}),
z.object({
type: z.literal('keypress'),
key: z.string()
})
])
app.post('/events', zValidator('json', eventSchema), (c) => {
const event = c.req.valid('json')
if (event.type === 'click') {
console.log(event.x, event.y) // 类型正确!
}
})Built-in Validator
内置验证器
For simple cases without external dependencies:
typescript
import { Hono } from 'hono'
import { validator } from 'hono/validator'
const app = new Hono()
app.post(
'/posts',
validator('json', (value, c) => {
const { title, body } = value
if (!title || typeof title !== 'string') {
return c.json({ error: 'Title is required' }, 400)
}
if (!body || typeof body !== 'string') {
return c.json({ error: 'Body is required' }, 400)
}
// Return validated data (shapes the type)
return { title, body }
}),
(c) => {
// data is typed as { title: string; body: string }
const data = c.req.valid('json')
return c.json({ post: data }, 201)
}
)适用于无需外部依赖的简单场景:
typescript
import { Hono } from 'hono'
import { validator } from 'hono/validator'
const app = new Hono()
app.post(
'/posts',
validator('json', (value, c) => {
const { title, body } = value
if (!title || typeof title !== 'string') {
return c.json({ error: '标题为必填项' }, 400)
}
if (!body || typeof body !== 'string') {
return c.json({ error: '正文为必填项' }, 400)
}
// 返回已验证数据(确定类型)
return { title, body }
}),
(c) => {
// data 类型为 { title: string; body: string }
const data = c.req.valid('json')
return c.json({ post: data }, 201)
}
)TypeBox Validation
TypeBox验证
typescript
import { tbValidator } from '@hono/typebox-validator'
import { Type } from '@sinclair/typebox'
const UserSchema = Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
age: Type.Optional(Type.Integer({ minimum: 0 }))
})
app.post('/users', tbValidator('json', UserSchema), (c) => {
const user = c.req.valid('json')
return c.json({ user }, 201)
})typescript
import { tbValidator } from '@hono/typebox-validator'
import { Type } from '@sinclair/typebox'
const UserSchema = Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
age: Type.Optional(Type.Integer({ minimum: 0 }))
})
app.post('/users', tbValidator('json', UserSchema), (c) => {
const user = c.req.valid('json')
return c.json({ user }, 201)
})Valibot Validation
Valibot验证
typescript
import { vValidator } from '@hono/valibot-validator'
import * as v from 'valibot'
const UserSchema = v.object({
name: v.string([v.minLength(1)]),
email: v.string([v.email()]),
age: v.optional(v.number([v.integer(), v.minValue(0)]))
})
app.post('/users', vValidator('json', UserSchema), (c) => {
const user = c.req.valid('json')
return c.json({ user }, 201)
})typescript
import { vValidator } from '@hono/valibot-validator'
import * as v from 'valibot'
const UserSchema = v.object({
name: v.string([v.minLength(1)]),
email: v.string([v.email()]),
age: v.optional(v.number([v.integer(), v.minValue(0)]))
})
app.post('/users', vValidator('json', UserSchema), (c) => {
const user = c.req.valid('json')
return c.json({ user }, 201)
})Standard Schema Validator
标准Schema验证器
Works with any validation library implementing the Standard Schema spec:
typescript
import { standardValidator } from '@hono/standard-validator'
import { z } from 'zod'
// Works with Zod, Valibot, ArkType, etc.
app.post('/users', standardValidator('json', z.object({
name: z.string(),
email: z.string().email()
})), handler)可与任何实现Standard Schema规范的验证库配合使用:
typescript
import { standardValidator } from '@hono/standard-validator'
import { z } from 'zod'
// 可与Zod、Valibot、ArkType等配合使用
app.post('/users', standardValidator('json', z.object({
name: z.string(),
email: z.string().email()
})), handler)File Upload Validation
文件上传验证
typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const uploadSchema = z.object({
file: z.instanceof(File).refine(
(file) => file.size <= 5 * 1024 * 1024,
'File must be less than 5MB'
).refine(
(file) => ['image/jpeg', 'image/png'].includes(file.type),
'Only JPEG and PNG allowed'
),
description: z.string().optional()
})
app.post('/upload', zValidator('form', uploadSchema), async (c) => {
const { file, description } = c.req.valid('form')
const buffer = await file.arrayBuffer()
// Process file...
return c.json({ filename: file.name, size: file.size })
})typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const uploadSchema = z.object({
file: z.instanceof(File).refine(
(file) => file.size <= 5 * 1024 * 1024,
'文件大小不能超过5MB'
).refine(
(file) => ['image/jpeg', 'image/png'].includes(file.type),
'仅支持JPEG和PNG格式'
),
description: z.string().optional()
})
app.post('/upload', zValidator('form', uploadSchema), async (c) => {
const { file, description } = c.req.valid('form')
const buffer = await file.arrayBuffer()
// 处理文件...
return c.json({ filename: file.name, size: file.size })
})Reusable Schema Patterns
可复用Schema模式
Create Schema Factory
创建Schema工厂
typescript
// schemas/common.ts
import { z } from 'zod'
export const paginationSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20)
})
export const idParamSchema = z.object({
id: z.string().uuid()
})
export const timestampSchema = z.object({
createdAt: z.string().datetime(),
updatedAt: z.string().datetime()
})
// Usage
app.get('/items/:id',
zValidator('param', idParamSchema),
zValidator('query', paginationSchema),
handler
)typescript
// schemas/common.ts
import { z } from 'zod'
export const paginationSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20)
})
export const idParamSchema = z.object({
id: z.string().uuid()
})
export const timestampSchema = z.object({
createdAt: z.string().datetime(),
updatedAt: z.string().datetime()
})
// 使用示例
app.get('/items/:id',
zValidator('param', idParamSchema),
zValidator('query', paginationSchema),
handler
)Extend Schemas
扩展Schema
typescript
const baseUserSchema = z.object({
name: z.string().min(1),
email: z.string().email()
})
const createUserSchema = baseUserSchema.extend({
password: z.string().min(8)
})
const updateUserSchema = baseUserSchema.partial()
const userResponseSchema = baseUserSchema.extend({
id: z.string().uuid(),
createdAt: z.string().datetime()
})typescript
const baseUserSchema = z.object({
name: z.string().min(1),
email: z.string().email()
})
const createUserSchema = baseUserSchema.extend({
password: z.string().min(8)
})
const updateUserSchema = baseUserSchema.partial()
const userResponseSchema = baseUserSchema.extend({
id: z.string().uuid(),
createdAt: z.string().datetime()
})Best Practices
最佳实践
1. Validate Early
1. 尽早验证
typescript
// CORRECT: Validation before any processing
app.post('/users',
zValidator('json', createUserSchema), // Validate first
async (c) => {
const data = c.req.valid('json') // Safe to use
return c.json({ user: data })
}
)typescript
// 正确:先验证再处理
app.post('/users',
zValidator('json', createUserSchema), // 先验证
async (c) => {
const data = c.req.valid('json') // 安全使用
return c.json({ user: data })
}
)2. Use Appropriate Targets
2. 使用合适的验证目标
typescript
// JSON for API bodies
zValidator('json', schema)
// Form for HTML forms
zValidator('form', schema)
// Query for URL parameters (remember coercion!)
zValidator('query', z.object({ page: z.coerce.number() }))
// Param for route parameters
zValidator('param', z.object({ id: z.string() }))typescript
// API体使用JSON
zValidator('json', schema)
// HTML表单使用form
zValidator('form', schema)
// URL查询参数使用query(记得转换类型!)
zValidator('query', z.object({ page: z.coerce.number() }))
// 路由参数使用param
zValidator('param', z.object({ id: z.string() }))3. Content-Type Matters
3. Content-Type很重要
typescript
// JSON validation requires Content-Type: application/json
// Form validation requires Content-Type: application/x-www-form-urlencoded
// or Content-Type: multipart/form-data
// Handle both:
const schema = z.object({ name: z.string() })
app.post('/data',
async (c, next) => {
const contentType = c.req.header('content-type')
if (contentType?.includes('application/json')) {
return zValidator('json', schema)(c, next)
} else {
return zValidator('form', schema)(c, next)
}
},
handler
)typescript
// JSON验证需要Content-Type: application/json
// 表单验证需要Content-Type: application/x-www-form-urlencoded
// 或Content-Type: multipart/form-data
// 同时处理两种情况:
const schema = z.object({ name: z.string() })
app.post('/data',
async (c, next) => {
const contentType = c.req.header('content-type')
if (contentType?.includes('application/json')) {
return zValidator('json', schema)(c, next)
} else {
return zValidator('form', schema)(c, next)
}
},
handler
)4. Lowercase Headers
4. 标头使用小写
typescript
// Headers must be lowercase in validation
zValidator('header', z.object({
'authorization': z.string(), // ✓ lowercase
'x-custom-header': z.string(), // ✓ lowercase
// 'Authorization': z.string(), // ✗ won't work
}))typescript
// 验证中标头必须使用小写
zValidator('header', z.object({
'authorization': z.string(), // ✓ 小写
'x-custom-header': z.string(), // ✓ 小写
// 'Authorization': z.string(), // ✗ 无效
}))Error Response Format
错误响应格式
Zod Flatten Format
Zod扁平化格式
typescript
app.post('/users', zValidator('json', schema, (result, c) => {
if (!result.success) {
return c.json({
success: false,
error: result.error.flatten()
}, 400)
}
}), handler)
// Response:
{
"success": false,
"error": {
"formErrors": [],
"fieldErrors": {
"email": ["Invalid email address"],
"age": ["Number must be greater than 0"]
}
}
}typescript
app.post('/users', zValidator('json', schema, (result, c) => {
if (!result.success) {
return c.json({
success: false,
error: result.error.flatten()
}, 400)
}
}), handler)
// 响应:
{
"success": false,
"error": {
"formErrors": [],
"fieldErrors": {
"email": ["无效的邮箱地址"],
"age": ["数字必须大于0"]
}
}
}Zod Issues Format
Zod问题格式
typescript
app.post('/users', zValidator('json', schema, (result, c) => {
if (!result.success) {
return c.json({
success: false,
errors: result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message
}))
}, 400)
}
}), handler)
// Response:
{
"success": false,
"errors": [
{ "field": "email", "message": "Invalid email address" },
{ "field": "age", "message": "Number must be greater than 0" }
]
}typescript
app.post('/users', zValidator('json', schema, (result, c) => {
if (!result.success) {
return c.json({
success: false,
errors: result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message
}))
}, 400)
}
}), handler)
// 响应:
{
"success": false,
"errors": [
{ "field": "email", "message": "无效的邮箱地址" },
{ "field": "age", "message": "数字必须大于0" }
]
}Quick Reference
快速参考
Zod Validator Targets
Zod验证器目标
| Target | Use Case | Example |
|---|---|---|
| JSON body | |
| Form data | |
| URL query params | |
| Route params | |
| Request headers | |
| Cookies | |
| 目标 | 使用场景 | 示例 |
|---|---|---|
| JSON体 | |
| 表单数据 | |
| URL查询参数 | |
| 路由参数 | |
| 请求标头 | |
| Cookies | |
Common Zod Types
常见Zod类型
typescript
z.string() // String
z.number() // Number
z.boolean() // Boolean
z.date() // Date
z.enum(['a', 'b']) // Enum
z.array(z.string()) // Array
z.object({}) // Object
z.optional(z.string()) // Optional
z.nullable(z.string()) // Nullable
z.coerce.number() // Coerce to number
z.string().default('val') // With defaulttypescript
z.string() // 字符串
z.number() // 数字
z.boolean() // 布尔值
z.date() // 日期
z.enum(['a', 'b']) // 枚举
z.array(z.string()) // 数组
z.object({}) // 对象
z.optional(z.string()) // 可选
z.nullable(z.string()) // 可空
z.coerce.number() // 转换为数字
z.string().default('val') // 带默认值Related Skills
相关技能
- hono-core - Framework fundamentals
- hono-rpc - Type-safe RPC with validation
- typescript-core - TypeScript patterns
Version: Hono 4.x, @hono/zod-validator 0.2.x
Last Updated: January 2025
License: MIT
- hono-core - 框架基础
- hono-rpc - 带验证的类型安全RPC
- typescript-core - TypeScript模式
版本:Hono 4.x, @hono/zod-validator 0.2.x
最后更新:2025年1月
许可证:MIT