メインコンテンツまでスキップ

Zod でスキーマバリデーション

前章では、バリデーションを if 文で 1 つずつ手書きしました。フィールドが増えるほどこのコードは膨らみ、「型」と「検証ルール」が二重管理になります。この章では、スキーマを 1 つ定義するだけで 実行時の検証静的な型 の両方が手に入る Zod を学びます。

この章で学ぶこと

  • Zod でスキーマを定義し、z.infer で型を導出する
  • React Hook Form と組み合わせてフォームを型安全にバリデーションする
  • safeParse で API レスポンスなど外部データを実行時に検証する
  • Zod v4 で変わった API の注意点
備考

この章では zod と、React Hook Form 連携用の @hookform/resolvers を使います。Zod は 2025 年に v4 へメジャーアップデートされており、本章は v4 を前提とします。

Zod とは

Zod は TypeScript ファーストのスキーマ宣言・検証ライブラリです。スキーマを 1 つ書くと、次の 2 つが同時に手に入ります。

  • 実行時バリデーション: 値がスキーマに合うかを実行時にチェックする
  • 静的な型: スキーマから TypeScript の型を導出する(手書きの type 定義が不要)

TypeScript の型は コンパイル時に消えるため、ユーザー入力や API レスポンスのような「実行時に初めて分かる値」は型だけでは守れません。Zod はこの境界を埋めます。

インストール

npm install zod
# React Hook Form と組み合わせる場合
npm install react-hook-form @hookform/resolvers

スキーマ定義の基本

z.object() でオブジェクトの形を宣言します。

import { z } from 'zod'

const userSchema = z.object({
name: z.string().min(1, '名前は必須です'),
email: z.email('メールアドレスの形式が正しくありません'),
age: z.number().int().min(0).max(120),
role: z.enum(['admin', 'member']),
})
  • z.string() / z.number() で基本型、.min() / .max() で制約を足す
  • z.email() はメール形式の検証(Zod v4 では z.string().email() から top-level の z.email() に変わりました)
  • z.enum() で取りうる値を限定する
  • 各メソッドの引数にエラーメッセージを渡せる

検証は parse(失敗時に例外を投げる)または safeParse(失敗を戻り値で返す)で実行します。フォームや外部データでは、例外に頼らず分岐できる safeParse が扱いやすいです。

const result = userSchema.safeParse({
name: '太郎',
email: 'taro@example.com',
age: 30,
role: 'member',
})

if (result.success) {
console.log(result.data) // 検証済みの値(型付き)
} else {
console.log(result.error.issues) // どのフィールドがなぜ失敗したか
}

z.infer で型を導出

スキーマから z.infer で型を取り出せます。型と検証ルールを 1 か所にまとめられるのが Zod の核心です。

type User = z.infer<typeof userSchema>
// 次の型と等価:
// { name: string; email: string; age: number; role: 'admin' | 'member' }

type を手書きすると、検証ルールを変えたときに型の更新を忘れがちです。z.infer ならスキーマを直すだけで型も追従します。

React Hook Form との連携

@hookform/resolvers/zodzodResolveruseForm に渡すと、Zod のスキーマがそのままフォームのバリデーションになります。

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const signupSchema = z.object({
email: z.email('メールアドレスの形式が正しくありません'),
password: z.string().min(8, '8 文字以上で入力してください'),
})

type SignupValues = z.infer<typeof signupSchema>

function SignupForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<SignupValues>({
resolver: zodResolver(signupSchema),
})

const onSubmit = (data: SignupValues) => {
// data はスキーマを通過した型安全な値
console.log(data)
}

return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<input type="email" {...register('email')} />
{errors.email && <span role="alert">{errors.email.message}</span>}
</div>
<div>
<input type="password" {...register('password')} />
{errors.password && <span role="alert">{errors.password.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>登録</button>
</form>
)
}

前章の手書きバリデーションと比べて、検証ロジックがスキーマに集約され、errors.email.message にスキーマで書いたメッセージがそのまま入る点に注目してください。

ヒント

React Hook Form 自体の API(register / handleSubmit / 非制御コンポーネント)は前章のフォームライブラリ節を参照してください。本章は Zod 側に focus しています。

API レスポンスの実行時検証

Zod の価値はフォームだけではありません。fetch で受け取った JSON は、TypeScript 上では型を自己申告しているだけで、実際にその形である保証はありません。

// よくある書き方: 実行時には何も検証していない
const res = await fetch('/api/users/1')
const user = (await res.json()) as User // as は嘘をつける

API の仕様変更やバックエンドのバグで形が変わっても、as では気づけません。safeParse を通せば、実行時に形を保証できます。

async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error('取得に失敗しました')

const json = await res.json()
const parsed = userSchema.safeParse(json)

if (!parsed.success) {
// 期待した形でない = バックエンドとの契約違反として扱う
throw new Error('レスポンスの形式が不正です')
}
return parsed.data // ここから先は型安全
}
備考

データフェッチング(TanStack Query)queryFn の中で safeParse を呼べば、キャッシュに入る前の段階で外部データを検証できます。型アサーション(as)を実行時の検証に置き換えるイメージです。

Zod v4 の注意点

Zod は 2025 年に v4 へメジャーアップデートされ、一部 API が v3 から変わっています。ネット上の記事は v3 前提のものが多いため、必ず Zod 公式ドキュメント で現行の API を確認してください。

  • 文字列フォーマットが top-level に: z.string().email()z.email()z.url() / z.uuid() なども同様)
  • エラーカスタマイズの統一: { message: ... }{ error: ... } に統合(message は当面動くが非推奨)
  • React Hook Form 連携: @hookform/resolvers/zod の Zod v4 対応版を使う

まとめ

  • Zod はスキーマ 1 つから 実行時検証静的な型z.infer)の両方を得る
  • zodResolver で React Hook Form のバリデーションをスキーマに集約できる
  • safeParse は API レスポンスなど外部データの実行時検証にも使える
  • 型推論の視点でさらに深掘りしたい場合は TypeScript で React を書く も参照

次に読む

フォームとバリデーションの基礎が固まりました。次は副作用を扱う useEffect に進みます。