hono-validation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hono 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等热门验证库无缝集成。验证通过中间件实现,允许在处理程序中类型安全地访问已验证的数据。
主要特性:
  • 内置轻量级验证器
  • 通过
    @hono/zod-validator
    提供一流的Zod集成
  • 标准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
undefined
bash
undefined

Zod (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
undefined
npm install @hono/standard-validator
undefined

Zod 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验证器目标

TargetUse CaseExample
json
JSON body
zValidator('json', schema)
form
Form data
zValidator('form', schema)
query
URL query params
zValidator('query', schema)
param
Route params
zValidator('param', schema)
header
Request headers
zValidator('header', schema)
cookie
Cookies
zValidator('cookie', schema)
目标使用场景示例
json
JSON体
zValidator('json', schema)
form
表单数据
zValidator('form', schema)
query
URL查询参数
zValidator('query', schema)
param
路由参数
zValidator('param', schema)
header
请求标头
zValidator('header', schema)
cookie
Cookies
zValidator('cookie', schema)

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 default
typescript
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