0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

openapi-fetchで型安全なAPI通信を実現する

Posted at

はじめに:API通信の課題

フロントエンドとバックエンドが別れているWebアーキテクチャでは、バックエンドAPIとの通信で以下のような課題に直面することがあります。

  • APIのエンドポイントやレスポンス形式を手動で管理していると、ミスやズレが発生しやすい
  • APIが変更されたときに、フロントエンドが気づかない
  • リクエストやレスポンスの型が曖昧で、TypeScriptの恩恵を活かしきれない

❶ エンドポイントやレスポンス形式を手動で管理 → ズレが生まれる

⚫︎ 例:

バックエンドが/api/userのレスポンスにemailフィールドを追加したが、フロントエンドは気づかず古い構造を使い続ける。

// 実際のAPIレスポンス(最新)
{
  "id": 1,
  "name": "Taro",
  "email": "Taro@example.com"
}

// フロントエンド側(古い記述)
type User = {
  id: number;
  name: string;
};

// ← emailがない。バグの温床に。

⚫︎問題:

  • 手書きの型定義やAPI呼び出しロジックが最新の仕様と一致している保証がない
  • 仕様変更に気づけず、ランタイムエラーになる

❷ API仕様の変更をフロントエンドが検知できない

⚫︎ 例:

バックエンドで /api/user/{id}/api/users/{id} に変更
でもフロントはURLを文字列で直接記述していた。

// 古いコード
fetch(`/api/user/${id}`); // ← ここは誰も気づかない

⚫︎ 問題:

  • エンドポイントが変わっても静的チェック(型エラー)されない
  • エラーは実行してみるまで気づけない(=品質の低下)

このような課題を解決するのが、openapi-fetchです。

❸ 型が曖昧で、TypeScriptの恩恵を活かしきれない

⚫︎ 例:

const res = await fetch('/api/user');
const data = await res.json();

// 型がないので補完されず、typoや誤解が起きやすい
console.log(data.emali); // ← email の typo に気づけない

⚫︎ 問題:

  • 型推論・補完が効かず、「型安全な開発」というTypeScriptの強みが発揮できない
  • 開発体験が悪く、バグも入り込みやすくなる

ここまでの問題を、openapi-fetchを導入することで以下のように解決できます。

課題 openapi-fetchでの解決策
手書き管理によるズレ OpenAPIの定義から型を自動生成。定義がそのままソースになる
フロントが仕様変更に気づけない 型の変更があればコンパイル時にエラーになるため、即検知できる
型が曖昧 Fetcher<paths>により型安全なリクエストとレスポンス補完ができる

openapi-fetchとは?

openapi-fetchは、OpenAPI仕様に基づいて型安全なAPIリクエストを実現するTypeScriptライブラリです。
以下のような特徴があります。

  • OpenAPIの定義(YAML/JSON)に基づいて正確な型付けができる
  • APIを呼び出すときに型補完・型チェックが効く
  • API仕様の変更に対して、型エラーで気づける

このライブラリは、openapi-typescriptとセットで使うことで最大の効果を発揮します。


なぜ openapi-typescript とセットで使うのか?

1. openapi-fetchは型駆動で動作するライブラリ

openapi-fetch は、OpenAPI仕様で定義されたAPI情報を TypeScript の型(特に paths 型)として受け取ることで、型補完されたfetchラッパーを提供するライブラリです。

つまり、「どんなエンドポイントが存在するか」「どんなレスポンスが返ってくるか」などの情報を、「型として知っていなければ使えない」という前提があります。

⚫︎ 例:

const fetcher = new Fetcher<paths>({
  baseUrl: "https://api.example.com"
});

この <paths> は自分で書くものではなく、OpenAPI仕様から生成される型です。

2. OpenAPI仕様 → TypeScript型 を自動生成するのが openapi-typescript

OpenAPI仕様は、.yaml.json の形式で以下のような情報を持ちます。

paths:
  /api/user:
    get:
      operationId: getUser
      responses:
        200:
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string

この情報を、手動でTypeScriptに書き起こすのは非効率で間違いのもとです。
そこで使うのが openapi-typescript。以下のコマンドで、型を自動生成できます:

npx openapi-typescript openapi.yaml -o src/schema.ts

この schema.tspathscomponents の型が生成されます。

3. 型補完の仕組み:fetcherは paths 型を元にリクエスト情報を制約する

たとえば以下のようなコードを書くと、エディタ上で補完が効くようになります。

import { Fetcher } from 'openapi-fetch';
import type { paths } from './schema';

const fetcher = new Fetcher<paths>({ baseUrl: 'https://api.example.com' });

const { data } = await fetcher.GET('/api/user'); // ← 補完される

このとき、/api/user のレスポンス型が以下のように推論されているのは、paths に型情報があるからです。

// dataの型
User[]  // 自動で補完される(手書き不要)

4. 自動生成された型のイメージ

以下は openapi-typescript によって生成された paths 型の一部です:

export interface paths {
  "/api/user": {
    get: {
      responses: {
        200: {
          content: {
            "application/json": User[]
          }
        }
      }
    }
  }
}

export interface User {
  id: number;
  name: string;
}

Fetcher<paths> を使うと、ここに定義された情報を元に型チェックが働くようになります。


openapi-fetch 導入ステップ(セットアップ手順)

ステップ1:必要なパッケージをインストールする

まずはプロジェクトに必要なライブラリを追加します。

インストールコマンド

npm install openapi-fetch
npm install -D openapi-typescript
  • openapi-fetch
    型安全なAPI通信を行うための本体ライブラリです。
  • openapi-typescript(開発依存)
    OpenAPI仕様ファイル(YAMLやJSON)から、TypeScriptの型を自動生成するツールです。開発時だけ必要なので -D オプション(devDependencies)を付けます。

ステップ2:OpenAPI仕様ファイルを用意する

次に、OpenAPI仕様ファイルを準備します。これがなければ openapi-fetch は使えません。

  • 通常、バックエンド開発者が用意している場合が多いです
  • または、Swagger Editorなどで自分で作成することも可能です

例:openapi.yaml

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /api/user:
    get:
      operationId: getUser
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string

ステップ3:型を自動生成する

ここで、openapi-typescriptを使って、OpenAPI仕様ファイルからTypeScriptの型定義を作成します。

型生成コマンド

npx openapi-typescript openapi.yaml -o src/schema.ts
  • openapi.yaml:元の仕様ファイル
  • src/schema.ts:出力先の型ファイル

これにより、例えば次のような型情報が自動生成されます。

例:schema.tsの中身(一部)

export interface paths {
  "/api/user": {
    get: {
      responses: {
        200: {
          content: {
            "application/json": User[];
          }
        }
      }
    }
  }
}

export interface User {
  id: number;
  name: string;
}

つまり、エンドポイントやレスポンス構造が型として定義されます。

ステップ4:Fetcherを作成する

次に、生成した型を使って、APIクライアント(Fetcher)を作成します。

例:src/apiClient.ts

import { Fetcher } from "openapi-fetch";
import type { paths } from "./schema"; // ステップ3で生成した型をインポート

export const fetcher = new Fetcher<paths>({
  baseUrl: "https://api.example.com",
});

基本的な使い方

openapi-fetchを使ったAPIリクエストの実践的なパターンを紹介します。

あらかじめ、Fetcherを初期化している前提で進めます。

import { Fetcher } from "openapi-fetch";
import type { paths } from "./schema";

const fetcher = new Fetcher<paths>({
  baseUrl: "https://api.example.com",
});

1. シンプルなGETリクエスト

もっとも基本的な使い方です。エンドポイントにGETリクエストを送信し、レスポンスを受け取ります。

const { data, error } = await fetcher.GET("/api/user");

if (data) {
  console.log(data[0].name); // レスポンスの型補完が効く
}
  • fetcher.GET(エンドポイント)の形式で呼び出します。
  • 戻り値のdataは、OpenAPI仕様に定義されているレスポンス型に従って型推論されます。
  • 型情報が付くため、プロパティ名の補完や型チェックが機能します。

2. Pathパラメータを含むGETリクエスト

URLにパスパラメータを含むエンドポイントにリクエストを送る場合です。

OpenAPI仕様に /api/user/{id} があるとき、

const { data, error } = await fetcher.GET("/api/user/{id}", {
  params: {
    path: { id: "123" },
  },
});
  • {}で囲まれた部分({id})をparams.pathに渡して埋め込みます。
  • パスパラメータは必須指定になっている場合が多いため、漏れると型エラーになります。

3. クエリパラメータを含むGETリクエスト

URLにクエリパラメータ(例:/api/user?role=admin)をつけてリクエストする場合です。

const { data, error } = await fetcher.GET("/api/user", {
  params: {
    query: {
      role: "admin",
    },
  },
});
  • params.queryにオブジェクト形式で渡します。
  • クエリパラメータも型定義されていれば、間違ったキーや型を渡すとコンパイルエラーになります。

4. JSON Bodyを送るPOSTリクエスト

POSTリクエストでデータをサーバーに送信する場合、bodyにリクエストボディを指定します。

例:ユーザー情報を登録するエンドポイント

const { data, error } = await fetcher.POST("/api/user", {
  body: {
    name: "Taro",
    email: "taro@example.com",
  },
});
  • bodyに渡すオブジェクトも、OpenAPI仕様で定義されている型に従います。
  • 型定義にないプロパティを渡すと、コンパイルエラーになります。
  • 逆に、必須項目を忘れてもコンパイルエラーになります。

5. Pathパラメータ+Bodyを両方使うPOSTリクエスト

PathパラメータとBodyを同時に使うエンドポイントの場合、params.pathbodyを両方渡します。

例:ユーザーにコメントを付けるエンドポイント

const { data, error } = await fetcher.POST("/api/user/{id}/comment", {
  params: {
    path: { id: "123" },
  },
  body: {
    message: "Hello World",
  },
});
  • params.pathにパスパラメータ
  • bodyにリクエストボディ
  • この2つを正しく分けて指定します。

まとめ

向いているプロジェクト

  • TypeScriptを使っているフロントエンド開発

    • 型補完をフル活用するため、TypeScriptプロジェクトと非常に相性が良い
  • バックエンドがOpenAPI仕様を管理している

    • FastAPI、NestJS、Spring Boot(OpenAPI Generator使用)など、OpenAPIを出力できるサーバーサイド技術と組み合わせると効果が最大化する
  • エンドポイント数やリクエスト種類が多い中〜大規模開発

    • API仕様が複雑なプロジェクトほど、型安全の恩恵が大きくなる
  • 複数人以上のチーム開発

    • チームメンバー間でAPI仕様の理解齟齬を防ぎ、作業の衝突や手戻りを大幅に減らすことができる
  • API仕様が頻繁に変わる(開発初期・機能追加フェーズ)

    • 型エラーで即座に仕様変更を検知できるので、仕様変更への耐性が強くなる
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?