T-CREATOR

Zodを活用した認証フローとAPIレスポンス設計の実践例を紹介

Zodを活用した認証フローとAPIレスポンス設計の実践例を紹介

Zodを導入する恩恵の一つとして、ユーザー入力やAPIとのやり取りにおいて型とデータ整合性を保証できることがあります。

今回は、ログイン・登録・認証チェックといった典型的なフローにZodをどう活用するか、API側とフロントエンド側での活用例を交えてわかりやすく解説していきます。


認証リクエストのバリデーション(ログイン編)

まずはログインフォームの入力バリデーションから。
バックエンドのリクエスト検証とフロントの型定義を共通化します。

共通スキーマ定義(shared-schema/auth.ts)

tsimport { z } from "zod";

export const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export type LoginRequest = z.infer<typeof loginSchema>;

バックエンドでの検証(NestJSの例)

tsimport { loginSchema } from "@shared-schema/auth";

const body = await request.json();
const result = loginSchema.safeParse(body);

if (!result.success) {
  return Response.json(
    { error: result.error.format() },
    { status: 400 }
  );
}

// 認証処理へ
const { email, password } = result.data;

フロントエンドでのフォーム型として使用(React Hook Form)

tsimport { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { loginSchema, LoginRequest } from "@shared-schema/auth";

const { register, handleSubmit, formState } = useForm<LoginRequest>({
  resolver: zodResolver(loginSchema),
});

ユーザー登録(Sign Up)のバリデーションと整合性チェック

ユーザー登録では、フィールド間の整合性が求められます。superRefineで実装可能です。

スキーマ定義(パスワード確認付き)

tsexport const signUpSchema = z
  .object({
    email: z.string().email(),
    password: z.string().min(8),
    confirmPassword: z.string(),
  })
  .superRefine(({ password, confirmPassword }, ctx) => {
    if (password !== confirmPassword) {
      ctx.addIssue({
        path: ["confirmPassword"],
        code: "custom",
        message: "パスワードが一致しません",
      });
    }
  });

export type SignUpRequest = z.infer<typeof signUpSchema>;

APIレスポンスの型安全化と整合性チェック

APIレスポンスの構造にもZodを活用できます。
これにより、サーバーから受け取ったデータの型安全性と妥当性を確保できます。

成功・エラー応答のUnion定義

tsconst successResponseSchema = z.object({
  status: z.literal("ok"),
  user: z.object({
    id: z.string().uuid(),
    name: z.string(),
    email: z.string().email(),
  }),
});

const errorResponseSchema = z.object({
  status: z.literal("error"),
  message: z.string(),
});

export const loginResponseSchema = z.union([
  successResponseSchema,
  errorResponseSchema,
]);

export type LoginResponse = z.infer<typeof loginResponseSchema>;

フロントエンドでのレスポンスチェック

tsconst res = await fetch("/api/login", {
  method: "POST",
  body: JSON.stringify(values),
});

const json = await res.json();
const result = loginResponseSchema.safeParse(json);

if (!result.success) {
  throw new Error("レスポンス構造が不正です");
}

if (result.data.status === "ok") {
  console.log("ようこそ", result.data.user.name);
} else {
  alert("ログインエラー: " + result.data.message);
}

JWT付きセッションチェックなどの発展パターン

APIの認証チェックでも、Zodは活躍します。
例えばJWTのペイロード検証や、APIレスポンスが未ログイン・ログイン済で分かれる場合などに便利です。

JWTペイロード検証

tsconst jwtPayloadSchema = z.object({
  sub: z.string().uuid(), // ユーザーID
  exp: z.number(),        // 有効期限
  iat: z.number(),        // 発行時刻
});

const decoded = jwt.verify(token, secret);
const result = jwtPayloadSchema.safeParse(decoded);

if (!result.success) {
  throw new Error("トークンが無効です");
}

サーバーレスポンスの設計パターン一覧

ケーススキーマ設計例
ログイン成功status + user
ログイン失敗status + message
セッション期限切れstatus: "unauthorized" + message
フィールドエラーstatus: "validation_error" + errors[]
任意フィールドのnull許容z.string().nullable() で対応

まとめ

Zodを用いることで、認証フローやAPI通信において次のような恩恵が得られます。

メリット内容
型とバリデーションの一元管理リクエスト・レスポンス・フォームすべてで型を共有
実行時安全性の向上サーバー側でのパースエラーや構造違反を早期検出
変更耐性のある設計スキーマの中心化により変更がプロジェクト全体に自動反映
型の補完と検証の自動化IDE補完と安全なデータ操作が保証され、開発効率が向上

実運用に即した設計こそ、Zodの真価が発揮される場面です。
認証・認可・セッション管理といったセンシティブな領域にも、安心して導入できる強力なツールなためぜひ活用してみて下さい。

記事Article

もっと見る