18
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Kubbを使ってOpenAPIスキーマからTanStack Queryのhooksを生成する

Last updated at Posted at 2024-06-28

はじめに

今回はKubbというコード生成ライブラリを紹介します

直近ではorvalを個人的に使っているのですが、Kubbというライブラリを見つけたので素振りがてら記事を書くことにしました
仕事で良くTanStack Queryを使用するので、TanStack Queryのhooks生成までを今回はやってみることにします

ちなみにKubbはスウェーデン生まれのスポーツで、スウェーデン語で「薪」を意味していているらしい(多分)

とりあえずインストール

Kubbは各機能をヘルパーやプラグインとして提供しているので、インストールするものは少し多いです

軽く概要↓

  • @kubb/cli:cliツール
  • @kubb/core:ファイルシステムの読み書きなど根幹の機能
  • @kubb/swagger-tanstack-query:TanStack Queryのhooksを生成するためのプラグイン
  • @kubb/swagger-ts:Swaggerファイルに基づいてTypeScript型を作成
yarn add @kubb/cli @kubb/core @kubb/swagger-tanstack-query @kubb/swagger-ts

設定ファイル

設定はorvalとほぼ変わらず、大枠inputとoutputで構成されています。
pluginを設定出来る点だけが違いそうですね。eslintのルールを足していく感覚と少しにているかも。

kubb.config.ts
import { defineConfig } from "@kubb/core"
import { pluginTanstackQuery } from "@kubb/swagger-tanstack-query"
import { pluginOas } from "@kubb/plugin-oas"
import { pluginTs } from "@kubb/swagger-ts"

export default defineConfig({
  root: ".",
  input: {
    path: "./petStore.yaml",
  },
  output: {
    path: "./src/gen",
    clean: true,
  },
  plugins: [
    pluginTanstackQuery({
      output: {
        path: "./hooks",
      },
    }),
    pluginTs(),
    pluginOas(),
  ],
})

生成してみる

これだけでOK
kubbkubb generateのエイリアスなのでkubbでも可

yarn kubb

jsonは要らなかったけどなぜか生成された。@kubb/plugin-oasが悪さしてそうだけど、gen時に足りないよって怒られたから入れてみた。

genディレクトリ配下↓

.
├── hooks
│   ├── index.ts
│   ├── useCreatePets.ts
│   ├── useListPets.ts
│   └── useShowPetById.ts
├── index.ts
├── schemas
│   ├── Error.json
│   ├── Pet.json
│   └── Pets.json
└── types
    ├── CreatePets.ts
    ├── Error.ts
    ├── ListPets.ts
    ├── Pet.ts
    ├── Pets.ts
    ├── ShowPetById.ts
    └── index.ts

APIクライアントは別途設定する必要がありそう。

src/hooks/useListPets.ts
import client from "@kubb/swagger-client/client";
import { useQuery } from "@tanstack/react-query";
import type { ListPetsQueryResponse, ListPetsQueryParams } from "../types/ListPets";
import type { UseBaseQueryOptions, UseQueryResult, QueryKey, WithRequired } from "@tanstack/react-query";

type ListPetsClient = typeof client<ListPetsQueryResponse, never, never>;
type ListPets = {
    data: ListPetsQueryResponse;
    error: never;
    request: never;
    pathParams: never;
    queryParams: ListPetsQueryParams;
    headerParams: never;
    response: ListPetsQueryResponse;
    client: {
        parameters: Partial<Parameters<ListPetsClient>[0]>;
        return: Awaited<ReturnType<ListPetsClient>>;
    };
};
export const listPetsQueryKey = (params?: ListPets["queryParams"]) => [{ url: "/pets" }, ...(params ? [params] : [])] as const;
export type ListPetsQueryKey = ReturnType<typeof listPetsQueryKey>;
export function listPetsQueryOptions<TData = ListPets["response"], TQueryData = ListPets["response"]>(params?: ListPets["queryParams"], options: ListPets["client"]["parameters"] = {}): WithRequired<UseBaseQueryOptions<ListPets["response"], ListPets["error"], TData, TQueryData>, "queryKey"> {
    const queryKey = listPetsQueryKey(params);
    return {
        queryKey,
        queryFn: async () => {
            const res = await client<ListPets["data"], ListPets["error"]>({
                method: "get",
                url: `/pets`,
                params,
                ...options
            });
            return res.data;
        },
    };
}
/**
 * @summary List all pets
 * @link /pets
 */
export function useListPets<TData = ListPets["response"], TQueryData = ListPets["response"], TQueryKey extends QueryKey = ListPetsQueryKey>(params?: ListPets["queryParams"], options: {
    query?: Partial<UseBaseQueryOptions<ListPets["response"], ListPets["error"], TData, TQueryData, TQueryKey>>;
    client?: ListPets["client"]["parameters"];
} = {}): UseQueryResult<TData, ListPets["error"]> & {
    queryKey: TQueryKey;
} {
    const { query: queryOptions, client: clientOptions = {} } = options ?? {};
    const queryKey = queryOptions?.queryKey ?? listPetsQueryKey(params);
    const query = useQuery<ListPets["data"], ListPets["error"], TData, any>({
        ...listPetsQueryOptions<TData, TQueryData>(params, clientOptions),
        queryKey,
        ...queryOptions
    }) as UseQueryResult<TData, ListPets["error"]> & {
        queryKey: TQueryKey;
    };
    query.queryKey = queryKey as TQueryKey;
    return query;
}

所感

細かく設定出来る反面、必要なモノの取捨選択の難易度が一定ありそうな雰囲気を感じました。楽に始められる点で考えるとorvalの方が、軍配が上がるかなという所感です

その他プラグインもこれだけ存在し、かなり自由度高く設定出来そうです。

  • @kubb/plugin-oas
  • @kubb/swagger-client
  • @kubb/swagger-ts
  • @kubb/swagger-zod
  • @kubb/swagger-zodios
  • @kubb/swagger-swr
  • @kubb/swagger-faker
  • @kubb/swagger-msw
  • @kubb/plugin-redoc
  • @kubb/parser-ts
  • @kubb/oas
  • @kubb/react

Kubbの行く末

orval作成時期は4年前、Kubbは1年前でありダウンロード数も2024/06/19時点で天と地の差がありますが、各機能をモジュールという形で提供していたり、作者がBlogで

At this moment Kubb is a library to create clients based on a Swagger file but Kubb was designed to also work with something else than Swagger. Because everything is a plugin, it will also make it really easy for other developers to create their plugin that implements for example a generation for GraphQL. See Create your plugin for more information about how to create your plugin.

このように言っていたりすることを考えると、今後のスタンダードになり得る候補の一つかなと思いました。

ダウンロード数↓
image.png

作成日↓
image.png

おわりに

株式会社HRBrainでは新しいメンバーを募集しています。

18
7
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
18
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?