TypeScriptでAPIレスポンスをZodやYupでスキーマ検証!初心者向け完全ガイド
生徒
「TypeScriptでインターネットからデータを読み込むとき、そのデータが本当に正しい形をしているか調べる方法ってありますか」
先生
「TypeScriptでは、ZodやYupという道具を使って、届いたデータの形を自動で厳しくチェックするスキーマ検証という仕組みがあります」
生徒
「インターネットからのデータは中身が変わるかもしれないから、調べる必要があるのですね。具体的にはどのように使うんですか」
先生
「それでは、基本的な使い方と重要性を順番に見ていきましょう」
1. API通信とレスポンスとは何か
インターネットの世界では、パソコンやスマートフォンが別の大きなコンピューターであるサーバーと情報のやり取りをしています。この情報のやり取りの仕組みをAPI通信と呼びます。例えば、お天気アプリを開いたときに、最新の天気予報データをサーバーにお願いして受け取るような動作のことです。
このとき、こちらからデータをお願いすることをリクエスト、サーバーから戻ってくるお返事データのことをレスポンスと呼びます。レスポンスは多くの場合、文字や数字が規則正しく並んだテキスト形式で届きます。このデータ形式を一般的にジェイソン形式と呼び、プログラムで扱いやすいように工夫されています。
プログラミング未経験の方は、お店で注文をして料理が届く様子を想像してください。注文がリクエストで、運ばれてくる料理がレスポンスです。しかし、時には注文したものと違う料理が届いたり、中身が崩れていたりすることもあります。パソコンの世界でも同じで、受け取ったデータが本当に予想通りの形をしているかを確かめることがとても大切になります。
2. なぜ型定義だけでは足りないのか
TypeScriptという言葉の通り、このプログラム言語はデータの種類である型をしっかりと決めて動かすのが得意です。しかし、ここで大きな落とし穴があります。TypeScriptの型チェックは、プログラムを書いている最中や、パソコンで動かせるように翻訳する段階だけでしか働きません。実際にインターネット上でプログラムが動いている本番の瞬間には、型をチェックする仕組みは消えてなくなっています。
つまり、プログラムを書いているときに、お天気データは必ず数字で届くと信じ込んで型を設定していても、実際に動かしたときにサーバー側の不具合で文字のデータが届いてしまう可能性があります。TypeScriptは、本番中にインターネットの向こう側からやってくる予測不可能なデータまでは監視できません。そのため、型定義だけを信じていると、間違ったデータがそのままプログラムを通り抜けてしまい、最終的に画面が真っ白になったりアプリが突然終了したりする原因になります。
そこで、実際に動いている本番のタイミングで、届いた瞬間のデータをその場で生で監視して、型が正しいかを判定する仕組みが必要になります。この本番中の監視体制を作ることが、安全なアプリケーション開発の第一歩です。
3. スキーマ検証という安全対策の基本
届いたデータが正しいかを調べるために、設計図を用意してはめ込み細工のようにチェックする方法をスキーマ検証と呼びます。ここでいうスキーマとは、データの設計図や形のルールのことです。例えば、名前は文字である、年齢は数字である、メールアドレスには特定の記号が含まれている、といったルールをあらかじめ紙に書いておくようなイメージです。
データが届いた瞬間に、この設計図と見比べます。もし設計図通りの綺麗なデータであれば、そのままプログラムの奥へと通します。しかし、もし設計図と違うおかしなデータが混ざっていた場合は、エラーとして検知してその場で侵入を食い止めます。これにより、プログラムの深いところで予期せぬエラーが発生することを防ぎます。
このスキーマ検証を簡単に行うために作られた有名な道具が、ZodとYupというライブラリです。これらを使うと、初心者でも数行のコードを書くだけで、非常に強力なデータの見張り番を設置することができます。今回はこの二つの道具の使い方を順番に解説していきます。
4. データを取得する基本の方法
まずは、検証する前の段階として、インターネットからデータを取ってくる標準的な方法を確認しましょう。今回は、ウェブブラウザに最初から備わっているFetchという機能を使って、架空のユーザー情報を取得する単純なプログラムを書いてみます。
async function getUserData() {
const response = await fetch("https://example.com/api/user");
const data = await response.json();
return data;
}
このプログラムは、指定した場所からユーザーのデータをダウンロードして、中身を取り出す処理をしています。しかし、この状態では、変数に入ったデータの中身が何であるか、プログラムは全く分かっていません。何でも受け入れる状態になってしまっているため、非常に危険です。次の章から、ここに安全対策の見張り番を追加していきます。
5. Zodを使った初めてのスキーマ検証
もっとも人気のある道具の一つであるZodを使ってみましょう。Zodでは、まずデータの設計図を作成します。今回は、ユーザーの識別番号である数字と、ユーザーの名前である文字の二つを持っているかを確認する設計図を作ります。
import { z } from "zod";
const userSchema = z.object({
id: z.number(),
name: z.string()
});
function validateWithZod(inputData: any) {
const result = userSchema.safeParse(inputData);
if (result.success) {
console.log("正しいデータです", result.data);
} else {
console.log("データに間違いがあります", result.error);
}
}
このコードでは、まずオブジェクトという塊の中に、数字であるべき項目と、文字列であるべき項目を定義しています。そして、検証を行う関数を使って、送られてきたデータが合格しているかを調べています。成功したか失敗したかが明確に分かれるため、条件に応じて処理を分けることが可能です。
実際に正しいデータをこの関数に渡した場合、プログラムは以下のような結果を出力します。
正しいデータです { id: 1, name: "太郎" }
もし、数字が入るはずの場所に文字が入っていたり、名前が抜けていたりした場合は、失敗の分岐に進み、どこが間違っているかを詳しく教えてくれる仕組みになっています。
6. Yupを使った初めてのスキーマ検証
次にもう一つの有名な道具であるYupを使った方法を解説します。基本的な考え方はZodと同じで、設計図を作ってから検証を行います。少しだけ書き方の雰囲気が異なりますが、役割は完全に同じです。
import * as yup from "yup";
const productSchema = yup.object({
title: yup.string().required(),
price: yup.number().required()
});
async function validateWithYup(inputData: any) {
try {
const validatedData = await productSchema.validate(inputData);
console.log("検証に成功しました", validatedData);
} catch (error) {
console.log("検証に失敗しました", error);
}
}
Yupの最大の特徴は、必須項目であることを明示的に指定する点にあります。文字列であり、かつ必ず存在していなければならない、というルールを設定しています。また、検証を行う際に、もしデータが間違っているとエラーが投げられる仕組みになっているため、例外処理の構文を使って失敗時の対応を書くのが一般的です。
このプログラムに正しい商品を渡して実行した場合、以下のような結果が得られます。
検証に成功しました { title: "鉛筆", price: 100 }
このように、ZodでもYupでも、本番環境で動く確実なデータの壁を作ることができます。どちらを使うかは開発チームの好みで決まりますが、現在の流行としてはZodの方がよく選ばれる傾向にあります。
7. 通信ライブラリAxiosと組み合わせる応用方法
実際の開発現場では、Fetchの代わりにAxiosという高機能な通信ライブラリがよく使われます。Axiosは、通信が失敗したときの処理などを細かく設定できるため、多くのシステムで採用されています。このAxiosを使ってデータを取得し、同時にZodで検証する実践的なコードを見てみましょう。
import axios from "axios";
import { z } from "zod";
const itemSchema = z.object({
id: z.string(),
stock: z.number()
});
async function fetchAndCheckItem() {
try {
const response = await axios.get("https://example.com/api/item");
const parsedResult = itemSchema.safeParse(response.data);
if (!parsedResult.success) {
console.log("データの形がおかしいです");
return;
}
console.log("安全なデータを確認しました", parsedResult.data.stock);
} catch (netError) {
console.log("通信そのものが失敗しました", netError);
}
}
この応用例では、二つの大きな守りを固めています。一つ目は通信そのものが切れてしまったり、サーバーが見つからなかったりしたときの守りです。二つ目は、通信は成功したけれど、届いたデータの中身がボロボロだったときの守りです。この二重のチェックを行うことで、どのような予期せぬ事態が起きても、プログラムが安全にエラーを処理して動き続けることができるようになります。
実行した際、データが綺麗であれば以下のように無事に中の数値を利用することができます。
安全なデータを確認しました 50
パソコンを触ったことがない方でも、このように段階を踏んでルールを決めておけば、機械が自動的にすべての異常を発見してくれるという便利さが分かっていただけると思います。インターネット連携アプリを作る際には、必ずこの検証の仕組みを導入するように心がけましょう。