💬

Zod 入門

に公開

Zod とは

Zod は TypeScript / JavaScript 向けのバリデーションライブラリです。
型定義と実行時バリデーションを一つの Zod スキーマで扱える点が特徴です。

Zod が解決する問題

TypeScript は型定義と型チェックを行うことができますが、それは開発時・コンパイル時にしか意味をなしません。
つまりコンパイル後の実行時には型チェックが行われないので、API レスポンスやフォーム入力などの外部入力データの型の信頼性を保証できません。

Zod はこれを解決するために「実行時の型バリデーション」と「Zod スキーマから TypeScript 型を生成する」機能を有しています。

基本的な使い方

スキーマを用いた型安全なバリデーション

import { z } from "zod";

const UserSchema = z.object({
  name: z.string(), // string 型
  age: z.number().int().positive(), // number型 かつ 正の整数
});

UserSchema.parse({ name: "Alice", age: 20 });
// => { name: "Alice", age: 20 }
UserSchema.parse({ name: "Alice", age: -1 });
// => ZodError

Zod スキーマを元に .parse() を用いてバリデーションを行います。
.parse()は型一致であればその値をそのまま、型不一致であればZodErrorを投げます。

try {
  UserSchema.parse(data);
} catch (e) {
  console.error("Validation failed", e);
}

このバリデーションを行うことにより型安全なデータのみをアプリ内で扱うことができます。
なお型が異なる場合でもエラーを返したくない場合は .safeParse() を使うことができます。

UserSchema.safeParse({ name: "Alice", age: 20 });
// => { success: true; data: { name: "Alice", age: 20 } }
UserSchema.safeParse({ name: "Alice", age: -1 });
// => { success: false; error: ZodError }

TypeScript の型生成

type User = z.infer<typeof UserSchema>;

const user: User = { name: "Alice", age: 20 };

Zod スキーマから TypeScript の型も得ることができます。

実際の使い方

TypeScript の型と違い、Zod では実行時に型チェックが行われるので、API リクエスト、フォーム入力時、環境変数の取得など、外部からのデータを受け取り、扱う際にメリットが大きいです。
.parse()で型安全が保証された値のみをアプリ内で扱えるため、不正値による実行時エラーの発生を防ぎやすくなります。
実行時の型チェックの他に Zod スキーマから TypeScript 型も生成できるため、スキーマの二重管理になることもありません。

API リクエストのサンプル

import { z } from "zod";

// レスポンスの形を Zod スキーマで定義
const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  age: z.number().int().positive().optional(),
});

// 型の自動生成
type User = z.infer<typeof UserSchema>;

// API から取得 & レスポンスのバリデーション
async function fetchUser(userId: string): Promise<User> {
  const res = await fetch(`https://api.example.com/users/${userId}`);

  if (!res.ok) {
    throw new Error("API request failed");
  }

  const json = await res.json(); // ここで型はまだ不明

  // ここで実行時バリデーション + 型付け
  // 型が正しくない場合は ZodError を投げる
  const user = UserSchema.parse(json);

  // ここから先は User 型として安全に扱える
  return user;
}

例外を投げない API リクエストのサンプル

型が正しくないデータが返された場合でも例外にしたくない場合は、safeParse()を使います。

async function fetchUserSafe(userId: string) {
  const res = await fetch(`https://api.example.com/users/${userId}`);
  if (!res.ok) throw new Error("API request failed");

  const json = await res.json();

  const result = UserSchema.safeParse(json); // safeParse()

  if (!result.success) {
    console.error("Validation error:", result.error);
    // null を返す・デフォルト値を返すなど
    return null;
  }

  // ここは result.data が User 型
  return result.data;
}

ちなみに API リクエストなら、fetchとバリデーションをしてくれるヘルパーをlib/apiClient.tsとかで配置すると、楽でプロジェクト全体で Zod を使う運用がしやすそうです。

まとめ

Zod は外部からのデータを実行時バリデーションで型安全に扱うことが出来るライブラリでした。

Zod が特に効果を発揮する部分としては以下が考えられます。

  • API リクエスト
  • フォーム入力
  • URL / クエリパラメータの取得
  • 環境変数の検証
  • Config ファイルの検証
  • JSON ファイルの検証

逆に言うと外部からのデータが流れてこない、内部の型だけで済む場合は TypeScript の型だけで十分そうです。

この記事では Zod の概要しか扱いませんでしたが、インストール方法や細かなスキーマの定義、エラー処理については公式ドキュメントが扱っています。

https://zod.dev/

Discussion