第19章 実行時バリデーション入門①:スキーマの役割📐✅

この章は「外部入力(=信用できない)を、ちゃんと中に入れられる形にする」ための超重要パートだよ〜!🚪🛡️✨ TypeScriptはコンパイル時の型チェックが得意だけど、実行時に来るデータ(APIリクエスト・フォーム・DB・外部API)は型なんて付いてないので、入口でスキーマ検証が必要になるよ🙂
1) 今日のゴール🎯✨
この章が終わると、こんなことができるようになるよ👇
- 「スキーマって何のため?」を説明できる📐🙂
- unknown入力を、スキーマで最低条件チェックしてから扱える🚪✅
- エラーを「まとめて」「分かりやすく」返す形を作れる🫶🧯
- “スキーマで守れること / 守れないこと” を分けられる🧠✨
2) スキーマってなに?超ざっくり言うと📐
スキーマ = データの「入場条件」を書いたルールブック📘✨ 「この形じゃないと入れません🙅♀️」を入口で判定する感じ!
たとえば会員登録なら👇
- email は文字列で、メール形式📩
- password は文字列で、8文字以上🔐
- plan は "Free" か "Pro" だけ🎫
- 余計なキー(知らない項目)は拒否したい🧹
こういう「形・型・範囲・必須/任意」をまず保証するのがスキーマの役割だよ🙂
3) スキーマが守るのは「最低限の安全」🛡️✨(超大事)
スキーマが得意なのは主にこれ👇
✅ スキーマが得意
- 必須チェック(ないとダメ)🧱
- 型チェック(string/number/boolean/array/object)🔎
- 範囲チェック(min/max/lengthなど)📏
- 形式チェック(email/uuid/date文字列など)🧾
- 配列の要素型チェック(stringの配列、など)📦
- “余計なキー” の拒否(strict)🧹
❌ スキーマが苦手 or 役割じゃない
- ドメイン固有の不変条件(例:誕生日から年齢計算して「18歳以上」など)🎂
- 複数フィールドにまたがるルール(例:start < end、割引の整合性)🧩
- DB参照が必要(例:メール重複、在庫確認)🗄️
- “意味のある値”の保証(例:Money/Quantity/EmailみたいなVO化💎)
👉 つまり、スキーマは「入口の一次検査」ってイメージがちょうどいいよ🙂✨ (次の章以降で、スキーマ→VO→ドメインの連携が山場になるよ🏔️🔗)
4) 2026時点の定番:どのバリデータ使う?🤔✨
代表的なのはこんな感じ👇(用途で選べばOK!)
- Zod:TypeScript-firstで人気。スキーマ定義→検証がシンプル📐✨ (Zod)
- Valibot:モジュラー志向で軽量さも意識されたスキーマライブラリ🧩✨ (valibot.dev)
- Ajv(JSON Schema):JSON Schemaで高速に検証、仕様・ツール連携が強い⚡📜 (ajv.js.org)
あと最近は、Standard Schemaっていう「バリデータ共通インターフェース仕様」も出てきてて、ツール側が複数ライブラリ対応しやすくなってきてるよ🧠✨ (Standard Schema)
この章のサンプルは、分かりやすさ重視で Zod(v4系) を使うね(Zod 4は安定版になってるよ)🙂✨ (Zod)
5) ハンズオン:会員登録の「入口スキーマ」を作ろう🚪📐✨
題材:POST /signup のリクエストボディがこう来る想定👇
- email: string(メール形式)
- password: string(8文字以上)
- plan: "Free" | "Pro"
- marketingOptIn: boolean(任意)
5-1) まずはスキーマを書く📐✨
import { z } from "zod";
export const SignupSchema = z
.object({
email: z.string().email("メール形式じゃないかも🥺"),
password: z.string().min(8, "パスワードは8文字以上にしてね🔐"),
plan: z.enum(["Free", "Pro"]),
marketingOptIn: z.boolean().optional(),
})
.strict(); // 👈 余計なキーを拒否(掃除大事🧹)
ポイント👇
.email()みたいな「形式チェック」便利📩.min(8)で長さ📏z.enum([...])で選択肢固定🎫.strict()で「知らない項目」を弾く🧹(地味に事故防止すごい)
5-2) unknown入力を受けて、safeParseで検証する🕵️♀️✅
「外から来るやつは信用しない」ので、まず unknown として扱うのが安全🙂
import { SignupSchema } from "./schemas/signup";
export function validateSignup(input: unknown) {
const result = SignupSchema.safeParse(input); // 例外を投げない版✨
if (!result.success) {
return {
ok: false as const,
// 👇 とりあえず“人間に見せやすい形”に整形
error: result.error,
};
}
return {
ok: true as const,
data: result.data,
};
}
parse() は失敗時に例外を投げるけど、API入口では safeParse が扱いやすいこと多いよ🙂
(Zodの parse / safeParse は基本機能として紹介されてるよ)(Zod)
6) エラーを「まとめて返す」🫶🧯✨(ここが優しさ!)
ユーザーに返すとき、エラーを1個ずつチマチマ返すより、まとめて返すほうが親切だよね🙂✨
Zodには「エラー整形」も用意されてるよ👇
flattenError を使うと、フォーム向けに扱いやすい形になる🧁✨ (Zod)
import { z } from "zod";
import { SignupSchema } from "./schemas/signup";
export function validateSignupForApi(input: unknown) {
const result = SignupSchema.safeParse(input);
if (!result.success) {
const flattened = z.flattenError(result.error);
return {
ok: false as const,
message: "入力にまちがいがあるよ〜🥺",
// formErrors: 全体に関わるエラー(余計キーとか)
formErrors: flattened.formErrors,
// fieldErrors: 各フィールドのエラー配列
fieldErrors: flattened.fieldErrors,
};
}
return { ok: true as const, data: result.data };
}
返却イメージ(例)👇
- formErrors:
["Unrecognized key: \"extraKey\""] - fieldErrors:
{ email: ["メール形式じゃないかも🥺"], password: ["8文字以上…"] }
「どこがダメ?」が一発で分かって優しい〜🫶✨
7) スキーマは「入口の契約書」📜✨(チームでも未来の自分でも助かる)
スキーマをちゃんと持つと、メリットがめちゃ多い👇
- 入口が1箇所にまとまる(チェックが散らばらない)🧹✨
- バリデーションが仕様として読める📜👀
- テストが超書きやすい🧪
- “余計なキー” を弾けて、想定外入力を減らせる🧯
- しかもZodは「スキーマ = 契約」みたいに考えやすいよ🙂 (danywalls.com)
8) ミニ課題:自分の題材の「最低条件スキーマ」作ってみよ🧱✨
あなたの題材(アプリ/機能)で、次を満たすスキーマを作ってね🙂
✅ お題
- 必須3つ以上(例:name/email/password)
- min/max どっちかを入れる(長さ or 数値範囲)📏
- enum(選択肢固定)を1つ入れる🎫
.strict()を付ける🧹- safeParseで検証 → flattenしてエラー返却🫶
「できたら勝ち」だよ〜🎉✨
9) AI活用(この章向け)🤖✨
スキーマ設計は、AIに“観点出し”させるのが超強い💪🙂
コピペ用プロンプト例👇
- 「この入力のバリデーション観点を20個出して(必須/範囲/形式/余計キー/境界値)」🧠
- 「ユーザーに優しいエラーメッセージ案を日本語で10個出して🫶」
- 「このスキーマで漏れやすい項目を指摘して🔍」
- 「不正入力の**テストケース(成功/失敗/境界値)**を列挙して🧪✨」
ただし!生成されたスキーマは、“不変条件が守られてる?”視点で必ず目視レビューしてね👀✨
10) まとめ:第19章で覚える合言葉🪄✨
- スキーマは「入口の一次検査」🚪✅
- unknown入力はまずスキーマに通す🕵️♀️
- エラーはまとめて返すと優しい🫶🧯
- スキーマで守れないルールは、次でVOやドメインで守る💎
次の第20章では、「検証したら型が付く」あの気持ちよさ😌✨(スキーマ→型推論)をガッツリ体験していくよ〜!🎉