Web アプリでは外部 API から取得したデータやユーザーが入力した情報を、そのまま扱うとバグやセキュリティリスクにつながります。
そこで登場するのが「ランタイムバリデーション」です。TypeScript の型はコンパイル時にしか効きませんが、実行時にデータ構造をチェックできるライブラリとして Zod が人気です。この記事では、初心者でもすぐに使える Zod の基本と、APIレスポンス・フォーム入力の2つのシーンでの具体的な使い方を紹介します。
1. Zod の基本
Zod はスキーマを宣言し、parse メソッドでデータを検証します。検証に失敗すると例外が投げられ、エラーメッセージが取得できます。
import { z } from "zod";
// 文字列の name と数値の age を持つオブジェクト
const UserSchema = z.object({
name: z.string(),
age: z.number().int().nonnegative(),
});
// 正常データ
UserSchema.parse({ name: "Taro", age: 28 }); // → { name: "Taro", age: 28 }
// 異常データ(例外が発生)
try {
UserSchema.parse({ name: "Hanako", age: -5 });
} catch (e) {
console.error(e);
}
UserSchema からは TypeScript の型が自動的に推論され、User 型として再利用できます。
type User = z.infer<typeof UserSchema>;
2. APIレスポンスの検証
外部サービスから JSON を取得したら、まず Zod で形を確かめましょう。以下は GitHub API の一部レスポンスを検証する例です。
import { z } from "zod";
const RepoSchema = z.object({
id: z.number(),
name: z.string(),
stargazers_count: z.number(),
private: z.boolean(),
});
type Repo = z.infer<typeof RepoSchema>;
async function fetchRepo(owner: string, repo: string): Promise<Repo> {
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
const json = await res.json();
// ここでランタイム検証
return RepoSchema.parse(json);
}
parse が成功すれば、以降のコードでは Repo 型が保証されるので、IDE の補完や型チェックがフルに活きます。
3. フォーム入力の検証
次に、React でのフォームバリデーションを Zod に任せる方法です。react-hook-form と組み合わせるとシンプルに書けます。
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const ContactSchema = z.object({
email: z.string().email(),
message: z.string().min(10, "10文字以上入力してください"),
});
type Contact = z.infer<typeof ContactSchema>;
export default function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm<Contact>({
resolver: zodResolver(ContactSchema),
});
const onSubmit = (data: Contact) => {
console.log("送信データ:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} placeholder="メールアドレス" />
{errors.email && <p>{errors.email.message}</p>}
<textarea {...register("message")} placeholder="メッセージ" />
{errors.message && <p>{errors.message.message}</p>}
<button type="submit">送信</button>
</form>
);
}
zodResolver が Zod スキーマを react-hook-form のバリデーションロジックに変換し、エラーメッセージを自動で取得できます。
4. スキーマを共有してコードを減らす
API から取得したデータをそのまま編集画面に流すケースでは、同じスキーマを API とフォームの両方で使えるのが大きなメリットです。
// 共通スキーマ
const ArticleSchema = z.object({
title: z.string().min(1),
body: z.string().min(10),
});
// API 取得
async function getArticle(id: number) {
const json = await (await fetch(`/api/articles/${id}`)).json();
return ArticleSchema.parse(json); // 型安全に取得
}
// フォーム
type Article = z.infer<typeof ArticleSchema>;
const { register, handleSubmit } = useForm<Article>({
resolver: zodResolver(ArticleSchema),
});
このようにスキーマを一元管理すれば、型の食い違いや重複したバリデーションロジックを防げます。
まとめ
- Zod はシンプルな宣言でランタイムバリデーションを実現
-
parseで例外が出たらすぐに問題を検知でき、型推論で開発効率が向上 - API とフォームの両方で同じスキーマを使うと保守性が高まる
実際に手を動かしてみると、バグの早期発見が格段に楽になります。ぜひプロジェクトに取り入れてみてください。