🧩

TypeScriptだけじゃ足りない?初学者が知るべき「Zod」でポートフォリオを堅牢にする方法

に公開

はじめに

プログラミングを学び始めて「TypeScriptを使っていれば型安全で安心!」と思っていませんか?
実は、TypeScriptだけでは防げない「実行時のエラー」という壁があります。
今回は、私がポートフォリオ制作を通じて学んだ、最強の門番「Zod」について解説します。

1. TypeScriptの「型」は実行時に消える?

TypeScriptはコードを書いている間(コンパイル時)は守ってくれますが、
ブラウザで動いている最中(ランタイム)にはその「型」の情報は消えてしまいます。
APIから返ってきたデータが想定と違う、ユーザーがフォームに予想外の入力をした等、

そこで必要になるのが、実行時にデータを検査するバリデーションライブラリ「Zod」です。

2. Zodとは?

Zodの役割は、外部からのデータを「スキーマ(ルール)」に照らし合わせてチェックすることです。

データの形が正しいか(文字列か、数値か)
中身が条件を満たしているか(8文字以上か、メールアドレス形式か)

ルールをクリアしたデータだけがプログラムの奥へと進めるため、バグや予期せぬ挙動を未然に防ぐことができます。

3. React Hook Formとの最強タッグ

フォーム管理ライブラリの「React Hook Form」と組み合わせることで、さらに強力になります。

React Hook Form: フォームの入力状態を管理する「管理人」
Zod: 入力された値が正しいか判定する「検査官」

この2つを連携させる(Resolverを使う)ことで、「入力した瞬間にエラーを出す」「型安全なデータだけを送信する」といった高度な機能を簡単に実装できます。

4. 実装例:会員登録フォームをガードする

私のポートフォリオ「SAKE STORY」での実装を例に紹介します。

事前準備
以下のライブラリをインストールしておきます。
npm install zod react-hook-form @hookform/resolvers

  1. バリデーションルールの定義
    まず、バリデーションのルールを1つのファイル(schemas.ts)にまとめます。
    こうすることで、後から「パスワードを10文字以上にしたい」と思っても、このファイルを直すだけで全ての画面に反映されます。
// 1. スキーマ定義 (schemas.ts)
import { z } from "zod";

export const signupSchema = z.object({
  email: z.string()
    .min(1, "メールアドレスを入力してください")
    .email("正しいメールアドレスの形式で入力してください"),
  
  password: z.string()
    .min(8, "パスワードは8文字以上で入力してください")
    .regex(/^[a-zA-Z0-9]+$/, "半角英数字で入力してください"),
});

// スキーマから型を抽出。これで「型定義の二重管理」から解放されます!
export type SignupInput = z.infer<typeof signupSchema>;
  1. フォームに適用する
    次に、React Hook Formを使って、このルールを実際の画面に適用します。
// 2. フォームの実装
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { signupSchema, type SignupInput } from "./schemas";

export default function SignupPage() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }, // 変数名を統一
  } = useForm<SignupInput>({
    resolver: zodResolver(signupSchema),
    mode: "onBlur",
  });

  const onSubmit = async (data: SignupInput) => {
    // dataは既にバリデーション済みかつ、SignupInput型として推論されているので安心
    const { error } = await supabase.auth.signUp({
      email: data.email,
      password: data.password,
    });
    // ...
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} placeholder="メールアドレス" />
      {errors.email && <p className="error-text">{errors.email.message}</p>}
      
      <input {...register("password")} type="password" placeholder="パスワード" />
      {errors.password && <p className="error-text">{errors.password.message}</p>}
      
      {/* disabled に isSubmitting を指定 */}
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "送信中..." : "登録する"}
      </button>
    </form>
  );
}

実際の画面上での変化(Zodの効果)
入力内容が不十分なときに、リアルタイムでエラーメッセージが表示されるようになり、プロジェクトの「堅牢性」が一段階アップしました。

5. Zodを導入してよかったこと

即時チェック:
mode: "onBlur" を設定したことで、入力欄から離れた瞬間に「その中身、ルール違反だよ」と教えてくれます。

ガードレール:
不正な入力(空欄や短いパスワード)がある限り、handleSubmit が実行されないため、サーバーへの無駄なリクエストも飛びません。

エンジニアとしての意識向上:
今回の導入を通じて、単に「動く」だけでなく、「外部データを疑い、堅牢なシステムを作る」という防御的プログラミングの重要性を学びました。これは、将来的に信頼性の高いシステムを構築できるエンジニアを目指す上でも、非常に大切な視点だと感じています。

6. 参考サイト

https://zod.dev/

https://react-hook-form.com/get-started#SchemaValidation

おわりに

Zodの基本的な使い方について紹介しました。この記事が、TypeScriptをより安全に使いこなす一助となれば幸いです。

以上

Discussion