TypeScriptとZodで型を自動同期!データバリデーションとスキーマ設計の初心者完全ガイド
生徒
「ホームページのフォームから入力されたデータが、正しい形式かどうかをチェックする方法はありますか?」
先生
「TypeScriptでは、Zodという便利な道具を使って、入力されたデータの安全性を強力にチェックすることができます。」
生徒
「データのチェックルールと、プログラムで使うデータの型を、別々に作ると管理が大変そうですね。」
先生
「その通りです。そこで今回は、データのチェックルールであるZodスキーマから、TypeScriptの型を自動的に作り出して同期させる、双方向設計の仕組みについて分かりやすく解説します!」
1. データバリデーションとは?
データバリデーションとは、画面から入力されたデータや、インターネットを通じて送られてきたデータが、あらかじめ決めたルール通りになっているかどうかを確認する検証作業のことです。日本語では入力チェックや妥当性確認とも呼ばれます。
例えば、パソコンの画面にある会員登録フォームを思い浮かべてください。メールアドレスを入力する欄に、普通の文章が入力されていたり、年齢を入力する欄に文字が入力されていたりすると、システムは正しく動かなくなってしまいます。最悪の場合、システムが壊れてしまう原因にもなります。
このようなトラブルを防ぐために、データを受け取る手前で、ルールに合っているかを厳しくチェックする必要があります。これがデータバリデーションの役割です。正しくないデータを見つけたら、画面にエラーメッセージを表示して、システムの中に不正なデータが入るのを未然に防ぎます。
2. TypeScriptの型とバリデーションの違い
プログラミングの世界には、データを安全に扱うための仕組みがいくつかあります。TypeScriptの特徴である型と、今回のテーマであるデータバリデーションには、大きな役割の違いがあります。この違いを理解することが、安全なプログラムを作るための第一歩です。
TypeScriptの型は、プログラムを書いている最中に、開発者をサポートするための仕組みです。設計図のようなもので、文字を入れる箱に数字を入れようとすると、パソコンが事前に注意してくれます。しかし、この仕組みはプログラムを実行する前の段階でしか機能しません。実際にインターネット上でプログラムが動いているときには、型という仕組みは消えてなくなってしまいます。
一方で、データバリデーションは、プログラムが実際に動いている最中に機能する仕組みです。利用者がフォームに偽のデータを入力したときや、外部のシステムから予期せぬデータが送られてきたときに、その瞬間のデータを直接監視して捕まえることができます。つまり、型は開発中のうっかりミスを防ぐものであり、バリデーションは本番環境での外部からの想定外のエラーを防ぐものという違いがあります。
3. Zodとはどのような道具か
Zodとは、データの形を定義して、その定義通りにデータが存在しているかを瞬時に判定してくれる、非常に人気のある外部ライブラリです。ライブラリとは、便利な機能があらかじめ詰め込まれた道具箱のようなものです。Zodを使うことで、複雑なバリデーションの仕組みを、短いコードで簡単に書くことができるようになります。
通常、文字の長さを調べたり、数値の範囲を調べたりするプログラムを自分で一から書くと、何行もの複雑な命令文が必要になります。しかし、Zodを使えば、文字の条件や数値の条件を、まるで箇条書きにするかのように分かりやすく記述することができます。
さらに、Zodの最も素晴らしい特徴は、データのチェックルールを作るのと同時に、そのルールからTypeScriptの型を自動的に作り出すことができる点にあります。これにより、二度同じような設計図を書く手間が省け、開発の効率が劇的に向上します。
4. Zodスキーマの基本的な作り方
それでは、実際にZodを使ってデータのルールを作ってみましょう。このルールのことを、専門用語でスキーマと呼びます。スキーマとは、データの構造や満たすべき条件を定義した設計図のことです。
まずは、最もシンプルなユーザー情報のスキーマを作成してみます。名前は文字であること、年齢は数字であること、という簡単なルールを定義します。以下のプログラムコードを見てみましょう。
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number()
});
このコードでは、まずZodの機能を読み込んでいます。そして、z.objectという命令を使って、複数のデータがまとまった塊を定義しています。その中で、nameという項目は文字列のデータであるz.stringを指定し、ageという項目は数値のデータであるz.numberを指定しています。これが、Zodにおける基本的なスキーマの作成方法です。
5. スキーマからTypeScriptの型を自動生成する
データのルールであるスキーマができたら、次はそのスキーマを基にして、TypeScriptで使える型を自動的に作成します。ここで登場するのが、Zodが提供しているz.inferという魔法のような機能です。インファーとは、日本語で推論するという意味があります。
これを使うことで、一度書いたスキーマの情報を読み取って、それと全く同じ構造を持つ型を自動的に作り出してくれます。手動で型を書き直す必要がないため、記述の間違いが絶対に起きません。実際のコードを確認してみましょう。
import { z } from "zod";
const itemSchema = z.object({
title: z.string(),
price: z.number()
});
type Item = z.infer<typeof itemSchema>;
新しく登場したtype Itemという部分が、TypeScriptの型を宣言している場所です。その後ろにあるz.infer<typeof itemSchema>という記述によって、先ほど作った商品のルールから、自動的に型が抽出されています。これにより、ルールを変更すると自動的に型も変更されるという、強力な連動環境が整います。
6. 型の同期と双方向設計のメリット
スキーマと型が自動的に連動する仕組みのことを、双方向設計や同期システムと呼びます。この設計手法を採用することには、開発において非常に大きなメリットがあります。プログラミング未経験の方にも分かりやすいように、その利点を解説します。
最大のメリットは、修正作業が劇的に楽になり、ミスの発生を完全に防げることです。もし、仕様の変更によって、会員情報に新しく電話番号の項目を追加することになったとします。もし同期をしていない場合、データのチェックルールを書き換え、さらにプログラミング用の型の定義も手動で書き換えなければなりません。一箇所だけ修正して、もう一箇所を忘れてしまうと、画面が動かなくなる原因になります。
しかし、Zodを使って型を自動同期させておけば、スキーマという大元のルールを一行書き換えるだけで、それを利用しているすべての型が自動的に最新の状態へと更新されます。一箇所を直すだけで全体に修正が行き渡るため、安全で効率的な開発が可能になります。
7. 実際にデータを検証してみよう
ルールを作り、型を同期させたら、最後にそのルールを使って、本物のデータが正しいかどうかを判定してみましょう。データを検証するときは、スキーマが持っているparseという命令を実行します。パースとは、データを解析してチェックするという意味の言葉です。
もしデータがルール通りであれば、そのまま綺麗なデータが返されます。もしデータに間違いがあれば、プログラムは即座にエラーを発生させて、それ以上進まないように身をてして守ってくれます。実際の検証プログラムの例を見てみましょう。
import { z } from "zod";
const profileSchema = z.object({
nickname: z.string(),
level: z.number()
});
const inputData = {
nickname: "勇者",
level: 10
};
const result = profileSchema.parse(inputData);
console.log(result);
このプログラムを実行すると、入力されたデータが完全にルールを満たしているため、無事に検証を通過して結果が画面に表示されます。実行結果は以下のようになります。
{ nickname: '勇者', level: 10 }
もし、数値が入るべき場所に文字が入っていた場合は、この段階で安全に停止し、どこが間違っているのかを教えてくれるメッセージが出力されます。このように、危険なデータを手前で弾き返すことができるのが、Zodによるデータバリデーションの魅力です。
8. より高度な条件を追加する応用方法
Zodのスキーマには、単に文字や数字といった大雑把な分類だけでなく、さらに細かい条件を付け加えることができます。例えば、文字であれば「最低でも5文字以上でなければならない」や、数値であれば「マイナスの値は認めずゼロ以上でなければならない」といった、実用的なルールです。
これらの細かい条件を組み合わせることで、より実戦向けの強力なバリデーションチェックを組み立てることができます。お祝いのメッセージと、評価の点数を想定した応用的なスキーマのコードを作成してみましょう。
import { z } from "zod";
const reviewSchema = z.object({
comment: z.string().min(10),
score: z.number().int().min(1).max(5)
});
type Review = z.infer<typeof reviewSchema>;
このプログラムでは、commentに対して.min(10)と書くことで、最低でも十文字以上の文章が入力されているかをチェックしています。また、scoreに対しては.int()で整数であることを求め、さらに.min(1)と.max(5)をつなげることで、一から五までの点数だけを許可する仕組みにしています。このような細かいカスタマイズも、型と完全に同期したまま簡単に行うことができます。