本文へジャンプ

データ検証の強い味方『Zod』のススメ

Posted by kenta sugiyama

フロントエンド開発においてもAIの導入やCI CDなど自動化をして効率化を図っている昨今ですが、
実装段階のツール、ライブラリとしてまだまだ便利なものがあります。
今回はフロントエンドでデータの検証を行い易くするTypeScript向けのライブラリ『Zod』についてご紹介します。

Zodとは

Zodとは、TypeScript向けのスキーマ宣言データ検証のためのライブラリです。
Zodを使用すると、型安全な方法でデータ構造を定義して、それに基づいてデータを検証することができます。
ZodはTypeScriptの型システムと統合されていて、コンパイル時に型エラーを検出しやすくしランタイムエラーを減らすのに役立ちます。
簡潔に言えば、Zodは型とデータの整合性を強化するためのツールです。

そもそもスキーマとは

スキーマはデータの構造やルールを事前に定義する設計図のようなもので、プログラムではデータが所定の形式に従っているかを検証するためにスキーマを使用します。
例えば、入力内容が正しいかどうかをチェックする際に、スキーマを使って「このフィールドは文字列でなければならない」とか「このフィールドは必須である」といったルールを設定します。
スキーマを使うことでデータが期待通りの形になっているかを事前に確認できますし、後でエラーが発生するリスクを減らすことができます。
これが複数のシステム間でデータをやり取りする際に非常に役立ちます。

基本的な使い方

Zodには色々な使い方が用意されていますが、基本的な部分を一部を抜粋して紹介します。

import { z } from "zod";

const schema = z.string();

スキーマschemaの値のバリデーションを定義します。
上記の例ではzodをimportしstring()メソッドを使用しています。
このschemaでバリデーションする場合、値がstring型であるかチェックされます。
他にもnumberやbooleanなど基本的な型のバリデーションを実装するためのメソッドが標準で用意されています。

z.string();    // string型
z.number();    // number型
z.bigint();    // bigint型
z.boolean();    // boolean型
z.date();    // date型
z.symbol();    // シンボル型
z.undefined();    // undefined型
z.null();    // null型
z.void();    // void型
z.any();    // any型
z.unknown();   // unknown型
z.never();    // never型

各データ型のスキーマ毎にさまざまな便利メソッドも用意されています。以下はstringのスキーマに用意されたメソッドです。

z.string().min(5);       // 5文字以上の文字列
z.string().max(5);       // 5文字以下の文字列
z.string().length(5);    // 固定幅の文字列
z.string().email();      // メールアドレス文字列
z.string().url();        // URL文字列
z.string().uuid();       // UUID文字列
z.string().regex(regex); // 正規表現にマッチする文字列
z.string().includes("apple");'  //  "apple"を含むかのバリデーション
z.string().startsWith("http://");  //   "http://"で始まるかのバリデーション
z.string().endsWith(".com");      // ".com"で終わるかのバリデーション
z.string().datetime();  // ISO 8601 形式の日時
z.string().nonempty();   // 空文字列以外の文字列

次に定義したスキーマをどのように使うかを簡単に説明します。使い方には2種類あります。

parse

parseの場合、バリデーションが成功するとresultにはバリデーションを通過した値が入り、失敗するとZodErrorがthrowされます。

const validateValue = (value: string) => {
  try {
    const result = schema.parse(value);
    return result;
  } catch(error) {
    console.error(error);
  }
}

errorの場合のZodErrorオブジェクトのissuesにエラー内容が入っています。非常に多くの情報を返してくれるのでわかりやすいです。

// ZodError.issues
[
   {
     "code": "invalid_type", // エラータイプ
     "expected": "string",   // 期待した型
     "received": "number",   // 受け取った値の型
     "path": [],      // エラーが発生したプロパティへのパス
     "message": "Expected string, received number" // エラー内容(スキーマ定義の段階でカスタマイズ可能)
   }
 ]

safeParse

バリデーション結果には成功、失敗を問わずオブジェクトが入ります。
オブジェクトのsuccessには成功したかどうかのフラグがbooleanで入っていて、dataには成功の場合バリデーションを通過した値、失敗の場合にはZodError型のオブジェクトが入ります。

const validateValue = (value: string) => {
  const result = schema.safeParse(value);
  // 成功時{ success: true, data: "fish" }
  // 失敗時{ success: false, data: ZodError }
  return result;
}

Zodを使わない場合との比較

Zodを使った場合とそうでなかった場合とで比較してみたいと思います。

  • email形式の文字列
  • 必須
  • 30文字以内

使わなかった場合

const REGEXP_EMAIL = /^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/;
const validateEmail = (value: string) => {
  if (value === '') {
    return '必須です';
  }
  if (!REGEXP_EMAIL.test(value)) {
    return 'EMAIL形式で入力してください';
  }
  if (value.length > 30) {
    return '30文字以内で入力してください';
  }
  return '';
}

使った場合

import { z } from "zod";

const emailSchema = z
  .string()
  .nonempty({ message: '必須です' })
  .email({ message: 'EMAIL形式で入力してください' })
  .max(30, { message: '30文字以内で入力してください' });

const validateEmail = (value: string) => {
  const result = emailSchema.safeParse(value);
  if (!result.success) {
    return result.error.errors[0].message;
  }
  return '';
}

コード量的に差はあまりありませんが、バリデーションに関する内容がスキーマで1箇所でまとめて定義できているので全体としてはより理解しやすいコードになると思います。
上記のコードではZodに用意されているemailのバリデーションを使用していますが、

const emailSchema = z
  .string()
  .nonempty({ message: '必須です' })
  .regex(/^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$/, { message: 'EMAIL形式で入力してください' })
  .max(30, { message: '30文字以内で入力してください' });

とすれば独自のルールでバリデーションすることも可能です。

まとめ

今回は簡単なバリデーションを例に紹介しましたが、他にもAPIを使用した組み込み時などで仕様書、サンプルデータを元にリクエストパラメータ、レスポンスデータのスキーマを定義しておけば実装後の検証がしやすくなります。
単純なバリデーション含めZodを使用しなくても実装をすることは可能ですが、正しく使用することで検証の工数の削減、最終的にはプロジェクト、サービスの価値の向上に繋がると思います。
いちライブラリではなくAIを使用して業務の効率化、自走化して安定性向上を模索している今日この頃ですが、そのあたりはまだまだ勉強中ですのでまたの機会に。

MONSTER DIVEでは、より効率的な実装を究めていく仲間を募集中です。 AIへの取り組みや社内勉強会など、貴方の力を伸ばせる・発揮できる環境で一緒に働いてみたい方は是非ご応募ください

Recent Entries
MD EVENT REPORT
What's Hot?