Zodバリデーション入門 - フォームとAPI入力を安全に扱う

初級 | 35分 で読める | 2025.12.04

公式ドキュメント

今回やること

Zodは、実行時に値を検証するためのTypeScript向けライブラリです。

TypeScriptの型は実行時のJSONを保証しません。フォーム入力やAPIリクエストの境界では、Zodのような実行時バリデーションが必要です。

この記事では次を学びます。

  • Zodの基本
  • スキーマ定義
  • バリデーション
  • 型推論
  • React Hook Formとの統合

Step 1: セットアップ

npm install zod

ポイント: ZodはスキーマからTypeScriptの型を自動推論できます。z.infer<typeof schema>で型定義の二重管理が不要になります。

Step 2: 基本的なスキーマ

import { z } from 'zod';

// プリミティブ型
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();

// オブジェクト
const userSchema = z.object({
 name: z.string().min(1, '名前は必須です'),
 email: z.string().email('有効なメールアドレスを入力してください'),
 age: z.number().int().positive().optional(),
});

// 型を推論
type User = z.infer<typeof userSchema>;

Step 3: バリデーション

// parse - 失敗時に例外
try {
 const user = userSchema.parse({
 name: 'Alice',
 email: 'alice@example.com'
 });
} catch (error) {
 if (error instanceof z.ZodError) {
 console.log(error.issues);
 }
}

// safeParse - 失敗時もオブジェクトを返す
const result = userSchema.safeParse(data);
if (result.success) {
 console.log(result.data);
} else {
 console.log(result.error.flatten());
}

Step 4: 高度なスキーマ

// 配列
const tagsSchema = z.array(z.string()).min(1).max(5);

// ユニオン
const statusSchema = z.enum(['active', 'inactive', 'pending']);

// 変換
const numberStringSchema = z.string().transform(val => parseInt(val, 10));

// リファインメント
const passwordSchema = z.string()
 .min(8, '8文字以上必要です')
 .refine(val => /[A-Z]/.test(val), '大文字を含めてください')
 .refine(val => /[0-9]/.test(val), '数字を含めてください');

// 条件付きバリデーション
const formSchema = z.object({
 password: z.string(),
 confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
 message: 'パスワードが一致しません',
 path: ['confirmPassword']
});

実践メモ: `@hookform/resolvers`を使えば、ZodスキーマをReact Hook Formのバリデーターとして利用できます。サーバー側でも同じ制約を検証してください。

Step 5: React Hook Formとの統合

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

const schema = z.object({
 name: z.string().min(1, '名前は必須です'),
 email: z.string().email('有効なメールアドレスを入力してください'),
});

type FormData = z.infer<typeof schema>;

function ContactForm() {
 const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
 resolver: zodResolver(schema)
 });

 const onSubmit = (data: FormData) => {
 console.log(data);
 };

 return (
 <form onSubmit={handleSubmit(onSubmit)}>
 <input {...register('name')} />
 {errors.name && <span>{errors.name.message}}

 <input {...register('email')} />
 {errors.email && <span>{errors.email.message}}

 <button type="submit">送信</button>
 </form>
 );
}

Step 6: APIバリデーション

// Next.js API Route
import { z } from 'zod';

const createUserSchema = z.object({
 name: z.string(),
 email: z.string().email(),
});

export async function POST(request: Request) {
 const body = await request.json();
 const result = createUserSchema.safeParse(body);

 if (!result.success) {
 return Response.json(
 { errors: result.error.flatten() },
 { status: 400 }
 );
 }

 // result.data は型安全
 const user = await createUser(result.data);
 return Response.json(user, { status: 201 });
}

バリデーションを置く場所

Zodは、外部から入ってくる値を安全に扱うための境界で特に効果を発揮します。フォーム入力、APIリクエスト、環境変数、外部APIレスポンスなど、信用できない値を受け取る場所で使います。

TypeScriptの型はコンパイル時の保証ですが、実行時に届くJSONの中身までは保証しません。Zodで実行時チェックを行うことで、想定外の値を早い段階で止められます。

実装時の観点

  • 入力用と出力用のスキーマを分けているか
  • エラーメッセージをユーザー向けに変換しているか
  • optionalとnullableの違いを意識しているか
  • 環境変数も起動時に検証しているか

動作確認

Zodを導入したら、成功ケースだけでなく失敗ケースを確認します。

確認項目入力例期待する結果
正常なemailtest@example.com検証成功
不正なemailabcemailエラー
短すぎる名前a文字数エラー
未指定{}必須エラー

safeParse を使うと、成功時と失敗時を result.success で分けられます。

練習

次の問い合わせフォーム用スキーマを作ってください。

  1. name は2文字以上
  2. email はメールアドレス形式
  3. message は10文字以上500文字以内
  4. safeParse で検証する
  5. 失敗時に error.flatten() の内容を画面表示用に変換する

次のステップ

APIで使う場合は Hono実践ガイドtRPC実践ガイド と組み合わせると理解しやすくなります。HTTPの失敗調査は APIエラーをHTTPから切り分ける練習 を確認してください。

まとめ

注意: .parse()はバリデーション失敗時に例外をスローします。エラーハンドリングが必要な場合は.safeParse()を使いましょう。

Zodは型推論とバリデーションを統合し、TypeScriptプロジェクトで型安全な入力検証を実現します。

参考リソース

← 一覧に戻る
PR
PR
PR
PR