フロントエンド

OpenAPI定義からReactのクライアントコード生成ツールにOrvalを採用した

mackey

こんにちは。エンジニアの牧田です。

先日から新規のプロジェクトでReactを書いていたのですが、その時にOpenAPI定義からReactのクライアントコードを生成するツールについていろいろと調査したので、今回はその紹介をしたいと思います。

結論としては、Orval を採用してみることにしました。

背景

スキーマ駆動開発はいいですよね。最近ではだいぶ浸透しているように感じます。スキーマ駆動開発には多くのメリットがありますが、開発者としてはやはりフロントエンドとバックエンドそれぞれの型定義が自動生成で一致するので安心感があります。

今回はスキーマとしてOpenAPIを使うという前提で話をしていきます。スキーマ駆動開発をする際の悩みどころとして、コード生成ツールとして何を使うかという問題があると思います。今回はフロントエンドに絞って実際に使用したツールを紹介しようと思います。

さまざまなコード生成ツール

コード生成ツールは数多くの種類が存在していて、どれを使うか決めるのは大変です。フロントエンドのクライアントコード生成ツールの例として、

などがあります。(他にもあると思います)

今回、これらのツールにはない機能として、React Query(TanStack Query)やSWRなどのReactのデータフェッチライブラリのコードを含んだcustom hooksまで自動生成してくれる機能があったら便利だなと思い、そのようなツールがないか探していました。

RTK Query

最初に見つけたツールが、RTK Query です。ツールというより、RTK Query自体は、状態管理ライブラリReduxの開発を便利にするRedux Toolkitの一部として存在するデータフェッチライブラリそのものなのですが。なんと、RTK Queryには公式の機能として、OpenAPI定義からのコード生成機能が存在します。

詳しくは、公式ドキュメントや、こちらの記事を参照していただきたいのですが、OpenAPIで定義したそれぞれのメソッドに対して、useXXXQueryやuseXXXMutationといったcustom hooksが生成されます。これを用いることで、自前でQuery keyなどを指定する必要なく簡単にデータ取得等の処理を書くことができます。

Orval

RTK Queryは実際に使ってみてもとても良い開発体験だったのですが、Reduxに依存しているため、Reduxを状態管理ライブラリとして用いるつもりがない場合もReduxの導入と設定が必要でした。データフェッチライブラリとしては、できればReduxに依存しないReact Query(TanStack Query)もしくはSWRを使用したかったので、他のツールもないか探していたところ、Orvalを見つけました。

Orvalに関しては、言及している日本語の記事も少なかったのですが、こちらの記事が参考になりました。実際に使用してみたところ、hooksの使用感もRTK Queryと同じ感じでとても良いツールだと感じました。

Orvalでは、React QueryやSWRのcustom hooksのほか、APIのモックとして使用できるMSWのコードまで生成することができます。MSWのコードではFakerを使用しており、レスポンスの型に応じたダミーデータを返すモックサーバーが作れるので、簡易的な用途としては十分使えるものになっています(後述するOrvalの設定ファイルでレスポンスをカスタマイズすることもできます)。

基本的な使い方

具体的な使い方に関しては、公式ドキュメントや、Githubにサンプルがいくつか用意されているのでそちらを参照するのがいいかと思いますが、簡単に紹介しようと思います。公式のサンプルの中で、React queryのものを例に紹介していきます。

以下コードの引用元:https://github.com/anymaniax/orval/tree/master/samples/react-query/basic

インストール

まず、Orvalのインストールです。この後紹介するorvalの設定ファイルをjsで書く場合はグローバルインストールでも良いですが、tsで書く場合はorvalのimportが必要なためローカルにインストールしましょう。(以降の例ではパッケージマネージャーにはyarnを使うこととします)

$ yarn add -D orval

Orvalの設定

設定ファイルはorval.config.tsに書きます。オプションの詳しい内容はこちらを参照してください。

import { faker } from '@faker-js/faker';
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      mode: 'split',
      target: 'src/api/endpoints/petstoreFromFileSpecWithTransformer.ts',
      schemas: 'src/api/model',
      client: 'react-query',
      mock: true,
      override: {
        mutator: {
          path: './src/api/mutator/custom-instance.ts',
          name: 'customInstance',
        },
        (省略)
        query: {
          useQuery: true,
          useInfinite: true,
          useInfiniteQueryParam: 'limit',
        },
      },
    },
    input: {
      target: './petstore.yaml',
      override: {
        transformer: './src/api/transformer/add-version.js',
      },
    },
  },
});

inputでは、生成元となるOpenAPIのYAML/JSONファイルを指定します。outputでは生成されるコードに関する各設定を行います。

実行

Orvalを実行するために、package.jsonのscriptsにコマンドを登録しておくと便利です。以下のように先ほどの設定ファイルへのパスを指定しましょう。yarn orvalを実行すると、コード生成が行われます。

{
  (省略)
  "scripts": {
    "orval": "orval --config ./orval.config.ts"
  },
}

生成されたコードの一部を見てみましょう。

export const listPets = (
  params?: ListPetsParams,
  version = 1,
  signal?: AbortSignal,
) => {
  return customInstance<Pets>({
    url: `/v${version}/pets`,
    method: 'get',
    params,
    signal,
  });
};

export const getListPetsQueryKey = (params?: ListPetsParams, version = 1) => [
  `/v${version}/pets`,
  ...(params ? [params] : []),
];

export const useListPets = <
  TData = Awaited<ReturnType<typeof listPets>>,
  TError = ErrorType<Error>,
>(
  params?: ListPetsParams,
  version = 1,
  options?: {
    query?: UseQueryOptions<
      Awaited<ReturnType<typeof listPets>>,
      TError,
      TData
    >;
  },
): UseQueryResult<TData, TError> & { queryKey: QueryKey } => {
  const { query: queryOptions } = options ?? {};

  const queryKey =
    queryOptions?.queryKey ?? getListPetsQueryKey(params, version);

  const queryFn: QueryFunction<Awaited<ReturnType<typeof listPets>>> = ({
    signal,
  }) => listPets(params, version, signal);

  const query = useQuery<Awaited<ReturnType<typeof listPets>>, TError, TData>(
    queryKey,
    queryFn,
    { enabled: !!version, ...queryOptions },
  ) as UseQueryResult<TData, TError> & { queryKey: QueryKey };

  query.queryKey = queryKey;

  return query;
};

listPetsは、useQueryのqueryFnに指定するfetcher関数です。getListPetsQueryKeyは、useQueryのqueryKeyに指定するキーを生成する関数で、APIのパスとパラメータごとにユニークなキーが作られます。useListPetsは実際にcustom hooksとして用いる関数で、中ではuseQueryを実行しています。

使う側では、以下のように簡単に使うことができます。

const { data, isLoading, error } = useListPets();

まとめ

今回は、OpenAPI定義からReactのクライアントコードを生成するツールに関する紹介をしました。さまざまなツールを調査した結果、現在はOrvalを採用して開発を行っています。OrvalによってAPI周りで書く必要があるコードのかなりの部分を自動生成することができ、開発効率が上がったと感じています。フロントエンドのOpenAPI定義からのコード生成ツールで何を使おうか悩んでいる方がいましたら、ぜひお試しください。

AUTHOR
mackey
mackey
記事URLをコピーしました