バリデーション
受信したリクエスト(JSON / クエリ / ヘッダー / フォーム)をスキーマで検証し、 通った値を型付きでハンドラに渡す仕組み。Zod / Valibot / TypeBox 等を使える。
定番: zod-validator
npm install zod @hono/zod-validator
JSON ボディの検証
import { Hono } from "hono"
import { z } from "zod"
import { zValidator } from "@hono/zod-validator"
const app = new Hono()
const userSchema = 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", userSchema), (c) => {
const data = c.req.valid("json") // 型付き!
// data: { name: string, email: string, age?: number }
return c.json({ created: data })
})
検証失敗時は自動的に 400が返る(カスタマイズ可能、後述)。
クエリの検証
const querySchema = z.object({
q: z.string().min(1),
page: z.coerce.number().int().min(1).default(1), // 文字列→数値
limit: z.coerce.number().int().min(1).max(100).default(20),
})
app.get("/search", zValidator("query", querySchema), (c) => {
const { q, page, limit } = c.req.valid("query")
return c.json({ q, page, limit })
})
パスパラメータの検証
app.get(
"/users/:id",
zValidator("param", z.object({ id: z.string().uuid() })),
(c) => {
const { id } = c.req.valid("param")
return c.json({ id })
},
)
フォームの検証
const formSchema = z.object({
title: z.string().min(1),
body: z.string().min(10),
})
app.post("/posts", zValidator("form", formSchema), async (c) => {
const data = c.req.valid("form")
return c.json(data)
})
ヘッダーの検証
const headerSchema = z.object({
"x-api-key": z.string().length(32),
})
app.use("/api/*", zValidator("header", headerSchema))
Cookie の検証
app.get(
"/me",
zValidator("cookie", z.object({ session_id: z.string() })),
(c) => c.json({ session: c.req.valid("cookie").session_id }),
)
複数の場所を同時に
app.put(
"/users/:id",
zValidator("param", z.object({ id: z.string().uuid() })),
zValidator("json", userSchema),
(c) => {
const { id } = c.req.valid("param")
const data = c.req.valid("json")
return c.json({ id, ...data })
},
)
エラーハンドリングのカスタマイズ
zValidator の第3引数でエラーレスポンスを上書きできる:
app.post(
"/users",
zValidator("json", userSchema, (result, c) => {
if (!result.success) {
return c.json({
error: "Validation failed",
issues: result.error.issues,
}, 400)
}
}),
(c) => { ... },
)
共通のエラーフォーマッタ
function formatError(result: any, c: any) {
if (!result.success) {
return c.json({
error: "Bad Request",
details: result.error.issues.map((i: any) => ({
path: i.path.join("."),
message: i.message,
})),
}, 400)
}
}
app.post("/users",
zValidator("json", userSchema, formatError),
(c) => { ... },
)
Zod の便利機能
型変換(coerce)
// クエリは文字列で来るので coerce
z.coerce.number() // "1" → 1
z.coerce.boolean() // "true" → true
z.coerce.date() // "2026-05-10" → Date
変換と精製
z.string()
.trim()
.toLowerCase()
.min(3)
.transform((v) => v.replace(/\s+/g, "-"))
refine で複雑な検証
z.object({
password: z.string(),
passwordConfirm: z.string(),
}).refine((d) => d.password === d.passwordConfirm, {
message: "パスワードが一致しません",
path: ["passwordConfirm"],
})
discriminated union
const eventSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
z.object({ type: z.literal("scroll"), delta: z.number() }),
])
Valibot(軽量代替)
Zod が大きすぎる時は Valibot。@hono/valibot-validator がある。
import { object, string, email, minLength } from "valibot"
import { vValidator } from "@hono/valibot-validator"
const schema = object({
name: string([minLength(1)]),
email: string([email()]),
})
app.post("/users", vValidator("json", schema), (c) => {
const data = c.req.valid("json")
return c.json(data)
})
OpenAPI 自動生成: Zod OpenAPI
@hono/zod-openapi を使うと、スキーマから OpenAPI ドキュメントが自動生成される。
npm install @hono/zod-openapi @hono/swagger-ui
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"
import { swaggerUI } from "@hono/swagger-ui"
const app = new OpenAPIHono()
const route = createRoute({
method: "post",
path: "/users",
request: {
body: { content: { "application/json": { schema: userSchema } } },
},
responses: {
201: {
description: "Created",
content: { "application/json": { schema: z.object({ id: z.string() }) } },
},
},
})
app.openapi(route, (c) => {
const data = c.req.valid("json")
return c.json({ id: "1" }, 201)
})
app.doc("/openapi.json", {
openapi: "3.0.0",
info: { version: "1.0.0", title: "My API" },
})
app.get("/docs", swaggerUI({ url: "/openapi.json" }))
/docs にブラウザでアクセスするとSwagger UIが表示される。
カスタムバリデータ
validator ヘルパーで独自バリデータを作れる:
import { validator } from "hono/validator"
app.post(
"/api",
validator("json", (value, c) => {
if (typeof value.name !== "string" || !value.name) {
return c.text("Invalid name", 400)
}
return value as { name: string }
}),
(c) => {
const { name } = c.req.valid("json")
return c.json({ name })
},
)
設計の指針
✓ すべての外部入力をスキーマで検証(JSON / form / query / header)
✓ スキーマを 1 か所で再利用(フロントとバックで共有も)
✓ クエリは z.coerce で文字列→数値変換
✓ エラーはクライアントが扱いやすいフォーマットで返す(path + message)