はじめに: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.ts
に paths
や components
の型が生成されます。
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.pathとbodyを両方渡します。
例:ユーザーにコメントを付けるエンドポイント
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仕様が頻繁に変わる(開発初期・機能追加フェーズ)
- 型エラーで即座に仕様変更を検知できるので、仕様変更への耐性が強くなる