導入
この記事では、OpenAPIから生成したTypeScriptの型定義をopenapi-fetchで利用し、型安全なAPI通信を実装する方法を説明します。
事前にopenapi-typescriptを導入し、schema.d.tsを生成しておく必要があります。未設定の場合は、先に以下の記事の手順を完了してください。
全体像
-
openapi-fetchのインストール - APIクライアントの作成
- APIクライアントを利用
openapi-fetchとは
openapi-fetchは、openapi-typescriptで生成した型定義を利用して、型安全にAPI通信を行うためのライブラリです。
OpenAPIに定義されたAPI仕様をもとに、次の項目をTypeScriptで型チェックできます。
| 型チェックの対象 | 検出できる誤りの例 |
|---|---|
| APIパス | OpenAPIに存在しないパスを指定している |
| HTTPメソッド | APIが対応していないメソッドを使用している |
| パス・クエリパラメータ | 必須パラメータの不足や型が間違っている |
| リクエストボディ | 項目名や値の型が間違っている |
| レスポンス | 定義されていないプロパティへアクセスしている |
レスポンス型はAPI定義から自動的に推論されるため、APIごとに手動で指定する必要はありません。
| ライブラリ | 役割 |
|---|---|
openapi-typescript |
OpenAPIからTypeScriptの型定義を生成する |
openapi-fetch |
生成された型を利用して型安全にAPI通信する |
openapi-typescriptが型定義の「生成」を担当し、openapi-fetchが生成された型定義の「利用」を担当します。
1. openapi-fetchのインストール
openapi-typescriptで生成した型定義をAPI通信に利用するため、openapi-fetchをインストールします。
npm install openapi-fetch
2. APIクライアントの作成
openapi-typescriptで生成した型定義を利用して、APIクライアントを作成します。
src/shared/api配下にclient.tsを作成します。
src/
└─ shared/
└─ api/
├─ schema.d.ts
└─ apiClient.ts
バックエンドへ直接アクセスする場合
import createClient from "openapi-fetch";
import type { paths } from "@/shared/api/schema";
export const apiClient = createClient<paths>({
baseUrl: import.meta.env.VITE_API_BASE_URL,
});
VITE_API_BASE_URL=http://localhost:8080
Viteのプロキシ経由でアクセスする場合
import createClient from "openapi-fetch";
import type { paths } from "@/shared/api/schema";
export const apiClient = createClient<paths>({});
3. APIクライアントを利用
作成したapiClientを利用してAPIへリクエストを送信します。
openapi-fetchは、主に次の値を返します。
-
data: 成功時のレスポンス本文 -
error: エラー時のレスポンス本文 -
response: HTTPステータスやヘッダーを持つResponse
GETリクエスト
データを取得する場合は、GETメソッドを使用します。
const { data, error, response } =
await apiClient.GET("/api/v1/users");
if (!response.ok || data === undefined) {
console.error(
"データの取得に失敗しました",
response.status,
error,
);
return;
}
リクエストパスに対応するレスポンス型は、OpenAPIから生成した型を基に自動で推論されます。
そのため、レスポンス型を手動で指定する必要はありません。
POSTリクエスト
データを登録する場合は、POSTメソッドを使用します。
const { error, response } = await apiClient.POST(
"/api/v1/users",
{
body: {
name: "山田太郎",
email: "taro@example.com",
},
},
);
if (!response.ok) {
console.error(
"データの登録に失敗しました",
response.status,
error,
);
return;
}
JSONを送信する場合は、bodyに送信するデータを指定します。
bodyの型はOpenAPIから生成されるため、存在しない項目を指定した場合や、値の型が異なる場合はTypeScriptの型エラーになります。
openapi-fetchがbodyをJSONへ変換して送信します。
パスパラメータを指定する
特定のデータを取得する場合など、URLにパスパラメータを含めることがあります。
例えば、次のパスからユーザー情報を取得する場合です。
/api/v1/users/{userId}
params.pathにパスパラメータの値を指定します。
const { data, error, response } = await apiClient.GET(
"/api/v1/users/{userId}",
{
params: {
path: {
userId: userIdNumber,
},
},
},
);
if (!response.ok || data === undefined) {
console.error(
"データの取得に失敗しました",
response.status,
error,
);
return;
}
params.pathのプロパティ名は、パス内の名前と一致させる必要があります。
- パス:
{userId} -
params.path:userId
存在しないパラメータ名を指定した場合や、必要なパラメータが不足している場合は、TypeScriptの型エラーになります。
おまけ:CSRFトークンを自動で送信する
バックエンドでCSRF対策が有効になっている場合、POSTやPUTなど、データを変更するリクエストではCSRFトークンの送信が必要になることがあります。
各APIの呼び出しで毎回ヘッダーを設定する代わりに、openapi-fetchのミドルウェアを利用できます。
Cookieを取得する関数
まず、Cookieから指定した値を取得する関数を用意します。
export function getCookie(name: string): string | null {
return (
document.cookie
// "key=value; key2=value2" の形式なので分割する
.split("; ")
// 指定したCookie名(例: XSRF-TOKEN)を探す
.find((row) => row.startsWith(name + "="))
// "key=value" から value のみ取得
?.split("=")[1] ?? null // 存在しない場合はnull
);
}
CSRF用のミドルウェアを登録する
作成したapiClientに、CSRFトークンを設定するミドルウェアを登録します。
import createClient, { type Middleware } from "openapi-fetch";
import type { paths } from "@/shared/api/schema";
import { getCookie } from "@/shared/cookie/cookie";
// CSRF対策が必要なHTTPメソッド
const csrfMethods = new Set([
"POST",
"PUT",
"PATCH",
"DELETE",
]);
const csrfMiddleware: Middleware = {
onRequest({ request }) {
// GETなど、対象外のリクエストはそのまま送信
if (!csrfMethods.has(request.method)) {
return;
}
// CookieからCSRFトークンを取得
const csrfToken = getCookie("XSRF-TOKEN");
if (!csrfToken) {
throw new Error(
"CSRFトークンをCookieから取得できませんでした",
);
}
// CSRFトークンをリクエストヘッダーへ設定
request.headers.set(
"X-XSRF-TOKEN",
csrfToken,
);
return request;
},
};
export const apiClient = createClient<paths>({});
// すべてのAPIリクエストにミドルウェアを適用
apiClient.use(csrfMiddleware);
まとめ
openapi-fetchを利用すると、openapi-typescriptで生成した型定義をAPI通信にも適用できます。APIパスやリクエスト、レスポンスを型安全に扱うことで、バックエンドの仕様とフロントエンドの実装のずれを見つけやすくなります。