TypeScriptの「盲点」を補完し、実行時の安全性を確保するスキーマ検証ライブラリの仕組みと、類似ライブラリとの比較
読了時間:約5分TypeScriptは強力な型システムを提供しますが、根本的な制約があります。それはコンパイル時(書いている時)にしか型チェックが行われないということです。
問題は、アプリケーションが外部からデータを受け取る場面で発生します。APIレスポンス、ユーザー入力、設定ファイルなど、外部から来るデータの構造は実行時まで分からないためです。
interface User { name: string; age: number; } // APIからデータを取得 const res = await fetch('/api/user'); const user: User = await res.json(); // 型は正しいはず... でも実際は? console.log(user.name.toUpperCase()); // 💥 実行時エラー: nameがundefined
TypeScriptは「APIがUserの形式を返す」と信じるだけです。実際に返ってきたデータの検証は行いません。
import { z } from 'zod'; const UserSchema = z.object({ name: z.string(), age: z.number(), }); const res = await fetch('/api/user'); const data = await res.json(); const user = UserSchema.parse(data); // ✅ 検証済み&型安全 console.log(user.name.toUpperCase()); // 安心して使える
Zodは実行時にデータを検証し、不正なデータはエラーとして報告します。通過したデータは型安全です。
Zodの核心的な価値は、スキーマ(データの設計図)を1つ定義するだけで、実行時の検証ルールとTypeScriptの型の両方を同時に得られることです。
従来は「TypeScript用の型定義」と「検証用のルール」を別々に書く必要がありました。これは二重管理となり、ズレが生じるリスクがありました。Zodはこの問題を解決します。
import { z } from 'zod'; // スキーマ定義(検証ルール + 型情報) const UserSchema = z.object({ email: z.string().email('有効なメールアドレスを入力してください'), age: z.number().min(18, '18歳以上である必要があります'), role: z.enum(['admin', 'user', 'guest']), }); // 型を自動推論(手動定義不要) type User = z.infer<typeof UserSchema>; // → { email: string; age: number; role: 'admin' | 'user' | 'guest' } // 検証(2つの方法) const result = UserSchema.safeParse(unknownData); if (result.success) { // result.data は型安全な User console.log(result.data.email); } else { // result.error に詳細なエラー情報 console.log(result.error.issues); }
外部ライブラリに一切依存しません。脆弱性リスクが低く、バージョン管理が容易です。
TypeScriptを前提に設計されており、型推論が非常に強力です。JavaScriptでも使用可能です。
.optional() や .extend() は新しいインスタンスを返し、元のスキーマを変更しません。予期しない副作用を防ぎます。
tRPC、React Hook Form、TanStack Routerなど、多くのライブラリとシームレスに連携できます。
どのフィールドが、どの理由で失敗したか、パス情報と共に詳細に報告されます。カスタムメッセージも設定可能です。
.transform() で検証と同時にデータ変換が可能。文字列を日付に変換するなど、入力→出力の型を変えられます。
Zodは唯一の選択肢ではありません。用途や要件に応じて、他のライブラリが適している場合もあります。
| ライブラリ | TypeScript連携 | バンドルサイズ | 主な用途 | 特徴 |
|---|---|---|---|---|
| Zod | 優秀 | ~13-45KB | フルスタック | 型推論が強力、エコシステム豊富 |
| Valibot | 優秀 | ~1-5KB | フロントエンド | 最軽量、Zodと似たAPI |
| Yup | 良好 | ~60KB | フォーム検証 | Formikと連携、歴史が長い |
| Joi | 要追加設定 | ~150KB | サーバーサイド | 機能が豊富、複雑な検証向け |
| AJV | 要追加設定 | ~85KB | 高性能API | 最速、JSON Schema準拠 |
Zodは単体でも強力ですが、他のツールと組み合わせることで真価を発揮します。特にフロントエンドからバックエンドまで同じスキーマを共有できるのが大きな利点です。
// server/router.ts(バックエンド) const CreatePostSchema = z.object({ title: z.string().min(1), content: z.string(), }); export const postRouter = router({ create: publicProcedure .input(CreatePostSchema) // ← Zodスキーマで入力を検証 .mutation(({ input }) => { // input は型安全な { title: string, content: string } return db.post.create({ data: input }); }), }); // client/form.tsx(フロントエンド) const { mutate } = trpc.post.create.useMutation(); // mutateの引数は自動的に { title: string, content: string } と推論される
プロジェクトの要件に応じて、最適なライブラリは異なります。以下のフローチャートを参考にしてください。
TypeScriptとの統合、豊富なエコシステム、活発なコミュニティにより、ほとんどのTypeScriptプロジェクトでZodは良い選択です。ただし、特定の要件(極小バンドルサイズ、最高性能、複雑な検証)がある場合は、専門ライブラリを検討してください。