本文へスキップ
ウェブエンジニア問題集
第1章

Zodとは何か — TypeScriptの型が実行時に消える問題

約9分
この章の目次開く

TypeScriptで書いているのに、実行したら Cannot read properties of undefined で落ちた——という経験はないでしょうか。しっかり型を付けたはずなのに、なぜ実行時に壊れるのか。

答えは単純で、TypeScriptの型はビルドの時点で消えてしまうからです。Zodは、その消えたあとの世界で「本当にその形をしているか」を確かめるためのライブラリです。

学習者学習者

APIのレスポンスにちゃんと User 型を付けたのに、実行したらundefinedで落ちたんだけど…型を付けた意味って何だったの?


TypeScriptの型は実行時には残らない

TypeScriptのコードは、ビルド時に型情報をすべて取り除かれてJavaScriptになります。型チェックのコードが1行も残らない、というのがポイントです。

// 書いたコード(TypeScript)
type User = { id: number; name: string };
 
function greet(user: User) {
  return `こんにちは、${user.name}さん`;
}
ts
// 実行されるコード(JavaScript)— 型はどこにもない
function greet(user) {
  return `こんにちは、${user.name}さん`;
}
js

つまり型は自分が書いたコードの内側だけを守る仕組みです。プログラムの外から届いたデータには、この安全ネットが届きません。

型注釈は「約束」であって「検査」ではない

外から届くデータに型を付けるとき、私たちはよくこう書きます。

type User = { id: number; name: string };
 
async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return (await res.json()) as User; // ← ここが問題
}
ts

as User は「このデータは User の形をしているはずだ」という宣言です。実際にそうなっているかは誰も確かめていません。APIが仕様変更で name を返さなくなっても、null を返してきても、この行は静かに通過します。そして画面を描画する段階になって初めて落ちます。

型注釈は「こうなっているはずだ」という約束であって、そうなっているかを確かめる処理ではありません。

守れる境界と守れない境界を整理すると、こうなります。

データの出どころ型注釈だけで守れるか理由
自分のコード内で作った値守れるビルド時にtscが検査する
APIのレスポンス守れない実体はただのJSON。形が変わっても気づけない
フォームの入力値守れないユーザーは何でも入力できる
URLのクエリパラメータ守れない手で書き換えられる
環境変数守れない型は常に string か undefined
localStorage / JSON.parse守れない戻り値は any 相当
DBから取得した行守れないスキーマ変更やNULLが素通りする

Zodがすること — 実行時に確かめて、型も付ける

Zodでは、まずスキーマ(データの形の定義)を書きます。

import { z } from 'zod';
 
const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.email(),
});
ts

このスキーマ1つから、2つのものが手に入ります。

  1. 実行時の検査 — 届いたデータがこの形をしているかを実際に確かめる
  2. 静的な型 — z.infer でTypeScriptの型を取り出せる
type User = z.infer<typeof userSchema>;
// { id: number; name: string; email: string } と同じ型になる
ts

先ほどの fetchUser をZodで書き直すと、こうなります。

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  const json = await res.json();
 
  const result = userSchema.safeParse(json);
  if (!result.success) {
    // 形が違う時点で気づける。画面が壊れる前に対処できる
    throw new Error('APIレスポンスの形式が想定と違います');
  }
 
  return result.data; // ここでの型は User
}
ts

safeParse は検査結果をオブジェクトで返します。成功すれば result.data に検査済みのデータが入り、その型は User として扱えます。「型を主張する」のではなく「確かめてから型が付く」という順番に変わったのが、この書き換えの本質です。

先生先生

ポイントは、型定義とバリデーションを二重に書かなくていいこと。type User を手で書いて、さらに検査用の if 文を手で書く——という重複が、スキーマ1つに片付くんだ。

二重管理がなくなる

Zodを使わない場合、同じ「Userの形」の情報をコードの2か所に書くことになります。

// 1か所目: 型
type User = { id: number; name: string; email: string };
 
// 2か所目: 検査(手書き)
function isUser(v: unknown): v is User {
  return (
    typeof v === 'object' &&
    v !== null &&
    typeof (v as User).id === 'number' &&
    typeof (v as User).name === 'string' &&
    typeof (v as User).email === 'string'
  );
}
ts

項目が1つ増えるたびに両方を直す必要があり、片方の修正漏れが起きます。Zodならスキーマだけを直せば、型も検査も同時に追従します。

チェックしている人のイラスト

Zodを置く場所は「外との境目」

Zodはアプリ内のすべてのオブジェクトに書くものではありません。外から中へデータが入ってくる境目に置きます。上の表で「守れない」となっていた場所が、そのまま置き場所になります。

  • APIのレスポンスを受け取った直後
  • リクエストのボディを受け取った直後(Route Handler、Server Action、Expressのハンドラなど)
  • フォームの送信値を受け取った直後
  • 環境変数を読み込むところ(起動時に1回)
  • JSON.parse や localStorage から値を復元したところ

一度検査を通したあとは、アプリ内部では普通のTypeScriptの型として扱えます。境目で1回止める、というのが基本の考え方です。

「なぜ検証が必要か」「フロントとサーバーのどちらで検証するか」といった設計面は バリデーション設計 — どこで何を検証するか で扱っています。この本はその「どう書くか」の部分を担当します。

この本が前提にするバージョン

本書のコードは Zod 4系(4.4.3)で実際に動かして確認しています。

よくある誤解

「TypeScriptを使っているからバリデーションは要らない」

この章の主題どおりです。型はビルド時に消えるため、実行時に届くデータには効きません。TypeScriptとZodは競合するものではなく、担当する時間帯が違う道具です。

効くタイミング守る対象
TypeScriptの型ビルド時(書いている最中)自分のコードの整合性
Zod実行時外から届くデータ

「Zodを入れたら全部のオブジェクトに書かないといけない」

境目だけで十分です。内部のロジックまでスキーマだらけにすると、記述量が増えるわりに得るものがありません。

「フロントで検証しているからサーバーでは省略できる」

フロントの検証は回避できるため、サーバー側の検証は省略できません。逆にフロント側を省略しても、体験が悪くなるだけで安全性は保たれます。詳しくは バリデーション設計の章を参照してください。

ちゃんと使うためのポイント

  • TypeScriptの型はビルド時に消える。外から届くデータは型注釈では守れない
  • 外部データへの as は検査ではなく、tscを黙らせているだけ
  • Zodはスキーマ1つから「実行時の検査」と「静的な型」の両方を作る
  • safeParse を通すと、型を主張するのではなく確かめてから型が付く
  • 置き場所はアプリ全体ではなく、外と中の境目

次の章では、実際にZodを インストールして動かす準備 をします。strict を有効にしていないとZodの型推論が正しく働かない、という落とし穴もここで扱います。

参考リンク

TypeScriptクイズに挑戦する型の基礎を4択クイズで確認して、Zodを読むための下地を固めよう