バリデーション

受信したリクエスト(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)