1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React + TypeScriptでOpenAPIの型を活用した型安全なAPI通信【openapi-fetch】

1
Posted at

導入

この記事では、OpenAPIから生成したTypeScriptの型定義をopenapi-fetchで利用し、型安全なAPI通信を実装する方法を説明します。

事前にopenapi-typescriptを導入し、schema.d.tsを生成しておく必要があります。未設定の場合は、先に以下の記事の手順を完了してください。

全体像

  1. openapi-fetchのインストール
  2. APIクライアントの作成
  3. 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

バックエンドへ直接アクセスする場合

src/shared/api/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,
});
.env.development
VITE_API_BASE_URL=http://localhost:8080

Viteのプロキシ経由でアクセスする場合

src/shared/api/apiClient.ts
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-fetchbodyを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.pathuserId

存在しないパラメータ名を指定した場合や、必要なパラメータが不足している場合は、TypeScriptの型エラーになります。

おまけ:CSRFトークンを自動で送信する

バックエンドでCSRF対策が有効になっている場合、POSTやPUTなど、データを変更するリクエストではCSRFトークンの送信が必要になることがあります。

各APIの呼び出しで毎回ヘッダーを設定する代わりに、openapi-fetchのミドルウェアを利用できます。

Cookieを取得する関数

まず、Cookieから指定した値を取得する関数を用意します。

cookie.ts
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トークンを設定するミドルウェアを登録します。

src/shared/api/apiClient.ts
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パスやリクエスト、レスポンスを型安全に扱うことで、バックエンドの仕様とフロントエンドの実装のずれを見つけやすくなります。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?