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

##  はじめに：API通信の課題

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

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

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

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

```ts
// 実際の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を**文字列で直接記述**していた。

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

#### ⚫︎ 問題：
- エンドポイントが変わっても**静的チェック（型エラー）されない**
- エラーは実行してみるまで気づけない（＝品質の低下）

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

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

#### ⚫︎ 例：
```ts
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ラッパー**を提供するライブラリです。

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

#### ⚫︎ 例：

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

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



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

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

```yaml
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`。以下のコマンドで、型を自動生成できます：

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

この `schema.ts` に `paths` や `components` の型が生成されます。



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

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

```ts
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` に型情報があるからです。

```ts
// dataの型
User[]  // 自動で補完される（手書き不要）
```


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

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

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

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

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

---

# openapi-fetch 導入ステップ（セットアップ手順）

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

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

### インストールコマンド

```bash
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`

```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の型定義**を作成します。

### 型生成コマンド

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

- `openapi.yaml`：元の仕様ファイル
- `src/schema.ts`：出力先の型ファイル

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

例：`schema.ts`の中身（一部）

```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`

```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を初期化している前提**で進めます。

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

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

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

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

```ts
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}` があるとき、

```ts
const { data, error } = await fetcher.GET("/api/user/{id}", {
  params: {
    path: { id: "123" },
  },
});
```

- `{}`で囲まれた部分（`{id}`）を`params.path`に渡して埋め込みます。
- パスパラメータは必須指定になっている場合が多いため、漏れると型エラーになります。


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

URLに**クエリパラメータ**（例：`/api/user?role=admin`）をつけてリクエストする場合です。

```ts
const { data, error } = await fetcher.GET("/api/user", {
  params: {
    query: {
      role: "admin",
    },
  },
});
```

- `params.query`にオブジェクト形式で渡します。
- クエリパラメータも型定義されていれば、間違ったキーや型を渡すとコンパイルエラーになります。

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

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

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

```ts
const { data, error } = await fetcher.POST("/api/user", {
  body: {
    name: "Taro",
    email: "taro@example.com",
  },
});
```

- `body`に渡すオブジェクトも、OpenAPI仕様で定義されている型に従います。
- 型定義にないプロパティを渡すと、コンパイルエラーになります。
- 逆に、必須項目を忘れてもコンパイルエラーになります。


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

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

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

```ts
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仕様が頻繁に変わる（開発初期・機能追加フェーズ）**
  - 型エラーで即座に仕様変更を検知できるので、仕様変更への耐性が強くなる