📌

openapi-ts で生成する 型定義と OpenAPI Client

2024/02/12に公開

はじめに

OpenAPI の定義から OpenAPI Client を生成するツールといえば、openapi-generatorがメジャーですが、動作させるために Java または Docker が必要となるため扱いづらいと感じていました。
また、Client/Server で様々な言語/FW に対応しているため必要な情報が調べにくく、またオプションや構文の対応状況に差異があり別ターゲットのナレッジが使いまわしにくい印象でした。

代替として、openapi-typescript と openapi-fetch が良さそうなので、使い方を見ていこうと思います。

https://openapi-ts.pages.dev/

https://www.npmjs.com/package/openapi-typescript

https://www.npmjs.com/package/openapi-fetch

前提

OpenAPI の定義は、Swagger が公開しているサンプルを利用します。

導入

openapi-typescript

openapi-typescript をインストールします

$ npm i -D openapi-typescript

生成のための scripts を追記します

package.json
{
  // ...
  "scripts": {
    // ...
+     "pregenerate": "npm run clean:generate",
+     "generate": "openapi-typescript https://petstore3.swagger.io/api/v3/openapi.json -o ./src/generated/oas.d.ts",
    // ...
+     "clean:generate": "node -e 'fs.rmSync(`src/generated`, {recursive:true, force:true})'",
    // ...
  },
  "devDependencies": {
    // ...
+     "openapi-typescript": "^6.7.4",
    // ...
    "typescript": "^5.3.3"
    // ...
  }
}

型定義を生成します。

$ npm run generate
#ref を利用している場合

@redocly/cli を利用し、事前に OpenAPI 定義を 1 ファイルにまとめます

$ npm i -D @redocly/cli
package.json
{
  // ...
  "scripts": {
    // ...
+    "oas": "redocly bundle [エントリポイントのOpenAPIファイル] --output [出力先]",
    // ...
    "pregenerate": "npm run clean:generate",
+     // ↓入力をoasの出力先に合わせる
    "generate": "openapi-typescript https://petstore3.swagger.io/api/v3/openapi.json -o ./src/generated/oas.d.ts",
    // ...
+    "clean:oas": "node -e 'fs.rmSync(`oas`, {recursive:true, force:true})'",
    "clean:generate": "node -e 'fs.rmSync(`src/generated`, {recursive:true, force:true})'",
    // ...
  },
  "devDependencies": {
    // ...
    "openapi-typescript": "^6.7.4",
    // ...
    "typescript": "^5.3.3"
    // ...
  }
}

$ npm run oas
$ npm run generate

openapi-fetch

openapi-fetch をインストールします

$ npm i openapi-fetch

client の作成

src/client.ts
import createClient from 'openapi-fetch';

import type { paths } from './generated/oas';

// -----

/**
 * Client
 */
export const client = createClient<paths>({
  baseUrl: `https://petstore3.swagger.io/api/v3`,
});

// -----

APi の呼び出し

const {
  data, // only present if 2XX response
  error, // only present if 4XX or 5XX response
  response,
} = await client.GET("/pet/findByStatus", {
  params: {
    query: {
      status: "available",
    },
  },
});

console.log("data", data);
console.log("error", error);
const {
  data, // only present if 2XX response
  error, // only present if 4XX or 5XX response
  response,
} = await client.GET("/pet/{petId}", {
  params: {
    path: {
      petId: 10,
    },
  },
});

console.log("data", data);
console.log("error", error);
const {
  data, // only present if 2XX response
  error, // only present if 4XX or 5XX response
  response,
} = await client.POST("/pet", {
  body: {
    id: 10,
    name: "doggie",
    category: {
      id: 1,
      name: "Dogs",
    },
    photoUrls: ["string"],
    tags: [
      {
        id: 0,
        name: "string",
      },
    ],
    status: "available",
  },
});

console.log("data", data);
console.log("error", error);

利用例

型定義の取得

import type { components } from "./generated/oas";

type schemas = components["schemas"];
type responses = components["responses"];
type parameters = components["parameters"];
type requestBodies = components["requestBodies"];
type headers = components["headers"];
type pathItems = components["pathItems"];

認証ヘッダーの付与

import createClient from "openapi-fetch";

import type { paths } from "./generated/oas";

// -----

/**
 * Client
 */
export const client = createClient<paths>({
  baseUrl: `https://petstore3.swagger.io/api/v3`,
  fetch: async (
    input: string | URL | globalThis.Request,
    init?: RequestInit
  ): Promise<Response> => {
    const token = "";

    return fetch(input, {
      ...init,
      headers: new Headers({
        ...init?.headers,
        Authorization: `Bearer ${token}`,
      }),
    });
  },
});

// -----

レスポンスヘッダーの取得

const { data, error, response } = await client.GET("/pet/findByStatus", {
  params: {
    query: {
      status: "available",
    },
  },
});

console.log("headers", response.headers);

さいごに

しっかり型補完が聞きつつも、シンプルな構成なため、要件に応じてカスタマイズできるため、推せるライブラリでした。

GitHubで編集を提案
株式会社ナンバーフォー

Discussion