🦔

oazapftsでOpenAPI specからTypeScriptクライアントを生成する

2023/02/10に公開

OpenAPIからのクライアント生成の老舗といえばOpenAPI Generatorだが、Java実行環境が必要で、テンプレートのメンテナンスなど考えることが多い。oazapftsというもう少しお手頃なジェネレータがあるのでここで紹介する。

特徴

Java不要

TypeScriptで書かれているため、ツールの実行にJavaを必要としない。

HTTPの詳細が露出しない定義

あとで出てくるが、URLのようなHTTPの詳細が見えるタイプのジェネレータとそうでないものがあり、oazapftsは見えない(見えにくいようにする)タイプ。例えばoperations["/pet/1"]ではなく、getPetByIdのような関数が生成される。

tree-shakingがされやすい造り

クライアントクラスがありまるごと全部抱えている、みたいなジェネレータもよくあるが、oazapftsは関数で提供する。例えば、以下のように利用可能だ。

import { getPetById } from "./my-generated-api.ts";

このため、bundleされるjsの中に必要な定義のみ取り込むことができる。

この形が嫌であれば、もちろん*でimportしたければしてもいい。

import * as api from "./my-generated-api.ts";
const res = api.getPetById(1);

単体での使い方

インストール

npmからインストールする。

$ npm i oazapfts

ジェネレータ実行

oazapftsコマンドを実行する。

$ oazapfts src/schema/foo-api.yaml src/api/foo-client.ts

package.jsonに入れておくと良い:

"scripts": {
    "generate": "yarn run generate:api",
    "generate:api": "oazapfts src/schema/foo-api.yaml src/api/foo-client.ts"
  },
}

関数を呼ぶ

生成foo-client.tsに関数群が生成されているのでimportして呼べば良い。

import {putDeviceById} from "../api/api-client";
const res = await putDeviceById(deviceId, { priority: values.priority });
if (res.status === 200) {
  console.log(res.data);
}

fetch optionはglobalにセットする手段もあるが、引数で渡すのがおすすめ。

const res = await putDeviceById(deviceId, values, { 
   headers: { "x-hogehoge": 123 }
});

また、毎回エラーチェックするのはしんどいので、"ok"というヘルパーを使うのも良い。

const data = await ok(getDeviceById(deviceId));

ここで書いたのはエラーを自動チェックしないexplicitモードというものだが、optimisticモードにするとチェックして例外にするので、必要に応じて切り替えてもいいかもしれない。が、次に説明するようなヘルパーをアプリケーション用に用意することをおすすめする。

Tanstack Query (React Query)との組み合わせ

Tanstack Queryと組み合わせるのは簡単で、queryFnで呼べば良い。試していないがSWRでも同様のはず。

const {isLoading, isError, data} = useQuery({
  queryKey: ["devices"],
  queryFn: () => ok(getDevices())
});

ただし、現実的にはContextから値を拾ってヘッダをつけたり、独自のエラーチェックをする処理があったりすると思うので、デフォルトoptionsの提供と、"ok"のようなエラーチェックを行うヘルパー関数をアプリケーション用に用意して一緒に使うことをおすすめする。

  const {apiRequest} = useApiHelper();

  const {isLoading, isError, data} = useQuery({
    queryKey: ["devices"],
    queryFn: () => {
       // ...
       return apiRequest((options) =>
          getDevices(options),
       );
   }
  });

TODO: サンプルレポジトリを上げる

他のツールとの比較

OpenAPI Generator

Javaでテンプレートベース。Javaが必要というのもあるが、テンプレートが好みではなく、カスタマイズするとなるとテンプレートのメンテナンスが必要そうなので避けている。

openapi2aspida

TypeScriptの型を使ってtype-safeにHTTPリクエストをしようというツールaspidaのスキーマをOpenAPI specから生成するためのツール。

使う側のコードがOpenAPIのoperationというよりもaspidaスキーマにべったりになりそうというのと、fetch / Tanstack Queryで使いたいのだが、axiosがメインぽく、@aspida/react-queryがあまりメンテナンスされていなそうなのでやめた。

openapi-typescript

OpenAPI specからTypeScriptの型を生成するツール。URLに対して型を生成するので、使う側に生URLが露出する。

例えばこんな感じ:

const findPetsByStatus = fetcher.path("/pet/findByStatus").method("get").create();
const { status, data: pets } = await findPetsByStatus({
  status: ["available", "pending"],
});

URLパスはAPIクライアントに閉じていてくれたほうが層として素直であると今の私は考えるので、そこまでwrapしてくれているoazapftsのほうが好ましいと感じている。

oazapftsの意味

The name comes from a combination of syllables oa (OpenAPI) and ts (TypeScript) and is pronounced speaking_head like the Bavarian O'zapt'is! (it's tapped), the famous words that mark the beginning of the Oktoberfest.

https://en.wikipedia.org/wiki/O'zapft_is!
お酒を飲むときの挨拶らしい。私は「おーえー、ざっぷ、えふてぃーえす」と脳内で読んでいる。

Discussion