Closed13

OpenAPI仕様書からフェッチクライアントを自動生成する

ミラミラミラミラ

背景

  • 既存Vueアプリで、リクエスト・レスポンスに型がない
  • Reactリプレイスにあたり型安全性を確保したい
  • 対象APIが300本以上あり、手動で型定義をしていられない

前提

  • OpenAPI仕様書がSSOT(Single Source of Truth)として存在すること

自動生成したいもの

  1. リクエスト・レスポンスパラメーターのTypeScript型(必須)
  2. 型安全なAPIクライアント(必須)
  3. 型安全なカスタムフック群
  4. 型安全なモックハンドラ群
ミラミラミラミラ

openapi-fetch × react-query

公式Examples

型付きクライアント

lib/api/index.ts
import createClient, { type Middleware } from "openapi-fetch";
import type { paths } from "./v1";

const throwOnError: Middleware = {
  async onResponse(res) {
    if (res.status >= 400) {
      const body = res.headers.get("content-type")?.includes("json")
        ? await res.clone().json()
        : await res.clone().text();
      throw new Error(body);
    }
    return undefined;
  },
};

const client = createClient<paths>({ baseUrl: "https://catfact.ninja/" });

client.use(throwOnError);

export default client;

APIアクセスフック

hooks/queries.ts
import { useQuery } from "@tanstack/react-query";
import type { ParamsOption, RequestBodyOption } from "openapi-fetch";
import type { paths } from "../lib/api/v1";
import client from "../lib/api";

type UseQueryOptions<T> = ParamsOption<T> &
  RequestBodyOption<T> & {
    // add your custom options here
    reactQuery?: {
      enabled: boolean; // Note: React Query type’s inference is difficult to apply automatically, hence manual option passing here
      // add other React Query options as needed
    };
  };

// paths
const GET_FACT = "/fact";

export function getFact({
  params,
  body,
  reactQuery,
}: UseQueryOptions<paths[typeof GET_FACT]["get"]>) {
  return useQuery({
    ...reactQuery,
    queryKey: [
      GET_FACT,
      // add any other hook dependencies here
    ],
    queryFn: async ({ signal }) => {
      const { data } = await client.GET(GET_FACT, {
        params,
        // body - isn’t used for GET, but needed for other request types
        signal, // allows React Query to cancel request
      });
      return data;
      // Note: Error throwing handled automatically via middleware
    },
  });
}
ミラミラミラミラ

We recommend the following wrapper, which works flawlessly with openapi-typescript:
openapi-msw by @christoph-fricke

推奨のmswラッパーあった、いいじゃん
https://github.com/christoph-fricke/openapi-msw

openapi-typescriptで生成した型を食わせるだけで、型安全なモックハンドラを生成できる模様
IFもopenapi-fetchといっしょでわかりやすい

ミラミラミラミラ

きちんと型生成するための推奨事項

https://openapi-ts.pages.dev/advanced#styleguide

ミラミラミラミラ

所感

openapi-typescript

  • もっぱら「OpenAPI to TS」の型生成に専念しており好き
  • 正しい型を生成するためのスタイルガイドまである
    • これ自体がコミュニケーションIFになる
  • 型生成が破綻したらその上に乗っているカスタムフックは結局死んでしまうわけで、そこだけに徹底してくれている安心感
  • npm trendsでも圧倒的

orval

  • カスタムフックまで自動生成してくれる
  • 自動生成されたreact-queryのカスタムフックも、重要なプラクティスを満たした良いコードだった
  • 一方で、react-query, swrなど複数のフェッチフックに対応し、かつvue, angular, svelteにまで対応しているいわゆる全部盛り
  • 「一つのことをうまくやれ」に強烈に反しているような
  • カスタムフック欲しさに、根幹となる型システムの生成までこの子に任せていいのか、本能レベルで恐怖を感じる
このスクラップは2ヶ月前にクローズされました