レビュー済み·難易度: 中級·更新: 2026-04-17

Zod で外部 API レスポンスを型安全に検証する

fetch した外部 API レスポンスを Zod スキーマで検証し、型安全なデータとエラーを分離して扱う実装例。

nextjsapivalidationzod

対応バージョン

nextjs 15zod 3

前提環境

TypeScript の基本と fetch API の使い方を理解していること

概要

外部 API のレスポンスは unknown 型として扱うのが安全。 Zod の safeParse を使うと、スキーマ不一致時に例外を throw せずに success / error で分岐できる。 型定義と検証ロジックを Zod スキーマで一元管理できるため、型キャストが不要になる。

インストール

npm install zod

実装例

// src/lib/schemas/post.ts
import { z } from "zod";

export const PostSchema = z.object({
  id: z.number(),
  title: z.string(),
  body: z.string(),
  userId: z.number(),
});

export const PostListSchema = z.array(PostSchema);

export type Post = z.infer<typeof PostSchema>;
// src/lib/api/posts.ts
import { PostListSchema, type Post } from "@/lib/schemas/post";

type FetchResult<T> =
  | { success: true; data: T }
  | { success: false; error: string };

export async function fetchPosts(): Promise<FetchResult<Post[]>> {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");

  if (!res.ok) {
    return { success: false, error: `HTTP error: ${res.status}` };
  }

  const json: unknown = await res.json();
  const result = PostListSchema.safeParse(json);

  if (!result.success) {
    return { success: false, error: result.error.message };
  }

  return { success: true, data: result.data };
}
// src/app/posts/page.tsx
import { fetchPosts } from "@/lib/api/posts";

export default async function PostsPage() {
  const result = await fetchPosts();

  if (!result.success) {
    return <p className="text-red-600">エラー: {result.error}</p>;
  }

  return (
    <ul className="space-y-2">
      {result.data.map((post) => (
        <li key={post.id} className="rounded border px-4 py-3 text-sm">
          <p className="font-medium">{post.title}</p>
          <p className="text-gray-500">{post.body}</p>
        </li>
      ))}
    </ul>
  );
}

ポイント

  • safeParse は例外を throw しないため、try/catch なしで success フラグで分岐できる
  • z.infer<typeof PostSchema> でスキーマから型を自動導出し、型定義の重複をなくす
  • unknown として受け取ってから safeParse に通すことで、実行時の型安全を確保できる
  • スキーマを src/lib/schemas/ に分離すると、フォームバリデーション(zod + react-hook-form)と同じスキーマを再利用できる

注意点

safeParse を使うと例外を throw せずに success / error を分岐できる。parse を使う場合は try/catch が必要。本番では unknown 型のレスポンスを必ず検証すること。

関連サンプル