12
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Open Api Generator(typescript-fetch)で自動生成したApiClientを使ったリクエストの前後に処理を追加する

Last updated at Posted at 2022-12-01

Open Api Generator(typescript-fetch)で自動生成したApiClientを使ったリクエストの前後に処理を追加する

Open Api Generatorとは

OpenAPI Generator allows generation of API client libraries (SDK > generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (both 2.0 and 3.0 are supported)

https://github.com/OpenAPITools/openapi-generator#overview より引用

とのこと。つまり、特定の言語に依存しないAPI記述仕様であるOpenAPI Specから各言語で利用するためのクライアントライブラリや、スタブサーバー、ドキュメントや設定を自動生成してくれるツールです。

何が嬉しいの?

OpenAPI Spec(以下OAS)はAPI記述の標準仕様です。
そのため、OASに基づいて作成された成果物を用いることで、APIの仕様について他者とコミュニケーションを取る際のコストを軽減することが期待できます。そして、作成された成果物からクライアントライブラリ等を自動生成することで、実装ミスの軽減したり、仕様と実装の乖離を防ぐ効果があります。

typescript-fetch Generator

typescript-fetch GeneratorはfetchAPIを用いたTypeScriptのクライアントライブラリのGeneratorです。類似のGeneratorとしてtypescript-axiosなどがあります。同じTypeScript用のGeneratorであっても、ものによってconfig可能な値や生成されるコードに違いがあるため、自身のプロジェクトが採用している技術に応じてGeneratorを選択すると良いでしょう。

Generatorの一覧はこちら

今回はTypeScript×fetchAPIを使っているプロジェクトへ導入するという前提で、typescript-fetch Generatorの簡単な使い方について解説していきます。

コードの生成

実際にツールを使ってコードを生成する方法はいくつかあるので公式の手順を参照してみてください。
今回はDockerを使った方法で生成してみます。

docker run --rm -v "${PWD}:/local" \
  openapitools/openapi-generator-cli:v6.0.0 generate \
  -i /local/openapi.yaml \
  -g typescript-fetch \
  -o /local/dist/target

上記のコマンドを実行すると、targetディレクトリ以下にファイルが生成されます。
なお、最新の安定版はv6.2.1ですが、生成されたコードが型エラーになってしまったため、v6.0.0を使用しています。

target.png

生成に使用したOpenAPIドキュメントはこちら

openapi: 3.0.0
info:
  version: 1.0.0
  title: Example
paths:
  /user:
    get:
      operationId: getUser
      tags:
        - User
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string

使い方

生成されたAPIクライアントのメソッドを実行すれば型付けされたレスポンスを得ることができます。便利ですね。

usage.png

Middlewareの実装

APIリクエストといえば、処理の前後に認証や後処理などの共通処理を挟み込むことがよくあります。
typescript-fetchには、それを叶えるMiddlewareという仕組みが用意されています。

詳しくは生成されたruntime.tsというソースを読んでいただきたいのですが、UserApiの継承元としてBaseApiというクラスが定義されており、コンストラクタやwithMiddlewareメソッドでMiddlewareという型を受け取れます。

型定義はこんな感じ

export interface Middleware {
    pre?(context: RequestContext): Promise<FetchParams | void>;
    post?(context: ResponseContext): Promise<Response | void>;
}

このMiddleware型を満たすようなインスタンスをUserApiに設定してあげると、pre関数でリクエストの前に、post関数でリクエストの後に実施したい処理を挟み込むことができます。

const middleware: Middleware = {
    pre: async (context: RequestContext) => {
        console.log("pre function")
        return {
            init: context.init,
            url: context.url
        }
    },
    post: async (context: ResponseContext) => {
        console.log("post function")
        return Promise.resolve(context.response)
    }
}
// 初期化時に渡す
const user = await (new UserApi(new Configuration({ middleware: [middleware] })).getUser())
// 後から設定もできる
const user = await (new UserApi().withMiddleware(middleware).getUser())

簡単ですね。(このあたりの話がドキュメントに書かれていると嬉しいのですが、私には見つけられませんでした。誰か見つけたら教えて下さい。)

さらに便利に

こんな感じすれば、各APIで共通処理を使いつつ必要なときにoverrideできて良さそう?(未検証)

const initClient = <T extends BaseAPI>(api: T, middleware?: Middleware): ApiClient<T> => {
    const defaultMiddleware = {} as Middleware // デフォルト動作を規定
    return middleware ? api.withMiddleware(middleware) : api.withMiddleware(defaultMiddleware)
}

type ApiClient<T extends BaseAPI> = {
    [P in keyof T]: T[P]
}

余談

生成されるコードのセミコロンの有無に一貫性がなく、夜も眠れなかったので、PR出したら無事マージされた。祝コントリビュート。

12
3
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
12
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?