🐾
OpenAPIでクライアントのプログラムを生成する
概要
OpenAPIを用いて、プロジェクト内のRest API実装から /v3/api-docs
を取得し、それを基にTypeScript(Axios)クライアントを生成します。生成はGradleで行い、以下のようなフローで生成されます。
- Rest API起動
- APIドキュメントを
/v3/api-docs
から取得 - TypeScript(Axios)クライアント生成
環境
- Java21
- Gradle 8.5
- Spring Boot 3.5.5
構成
openapi-generate-example
|-- generated // OpenAPI Generatorの出力先
|-- src
| |-- main
| |-- java
| |-- org
| |-- example
| |-- Application.java
| |-- ExampleController.java // Rest API
| |-- ExampleRequest.java // Rest API のリクエスト
| |-- ExampleResponse.java // Rest API のレスポンス
|-- build.gradle.kts
|-- gradlew
|-- gradlew.bat
|-- settings.gradle.kts
Gradle
プラグイン
plugins {
id("java")
id("org.springdoc.openapi-gradle-plugin") version "1.9.0"
id("org.openapi.generator") version "7.15.0"
}
プラグイン名 | 役割 |
---|---|
java | ビルド |
org.springdoc.openapi-gradle-plugin | /v3/api-docsを取得するタスクを提供 |
org.openapi.generator | クライアントのソースコードを生成するタスクを提供 |
依存関係
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:3.5.5")
implementation("org.springframework.boot:spring-boot-starter-validation:3.5.5")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
}
OpenAPI ドキュメント取得
openApi {
apiDocsUrl.set("http://localhost:8080/v3/api-docs")
outputDir.set(file("build/openapi"))
outputFileName.set("openapi.json")
waitTimeInSeconds.set(30)
}
http://localhost:8080/v3/api-docs
から定義を取得し、build/openapi
ディレクトリにOpenAPI定義を openapi.json
というファイル名で作成します。
TypeScript Axiosクライアント生成
openApiGenerate {
generatorName.set("typescript-axios")
inputSpec.set(file("build/openapi/openapi.json").absolutePath)
outputDir.set(file("generated").absolutePath)
cleanupOutput.set(true)
configOptions.set(
mapOf(
"withSeparateModelsAndApi" to "true",
"useSingleRequestParameter" to "true",
"apiPackage" to "api",
"modelPackage" to "model"
)
)
}
tasks.named("openApiGenerate") {
dependsOn("generateOpenApiDocs")
}
build/openapi/openapi.json
を読み取って、generated
ディレクトリにTypeScript Axiosクライアントを生成します。configOptions
に生成時のオプションを設定します。
オプション名 | 説明 |
---|---|
withSeparateModelsAndApi | APIとモデルを別ファイルで生成する |
useSingleRequestParameter | API呼び出し時の引数をまとめてひとつのオブジェクトとして扱う |
apiPackage | 生成されるAPIの配置ディレクトリ |
modelPackage | 生成されるモデルの配置ディレクトリ |
Java
Application.java
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
ExampleController.java
package org.example;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class ExampleController {
@PostMapping
public ExampleResponse post(@RequestBody ExampleRequest request) {
return new ExampleResponse("Example", 1L);
}
}
ExampleRequest.java
package org.example;
import jakarta.validation.constraints.NotNull;
public record ExampleRequest(@NotNull String value1, Long value2) {
}
ExampleResponse.java
package org.example;
public record ExampleResponse(String value1, Long value2) {
}
実行方法
./gradlew openApiGenerate
生成結果
generated
|-- api
| |-- example-controller-api.ts
|-- model
|-- example-request.ts
|-- example-response.ts
上記は生成結果からAPIとモデルを抜粋したものです。example-request.ts
を確認すると以下のようになっており、ExampleRequest.java
と対応していることが分かります。@NotNull
を付けているため value1
は必須になっています。
export interface ExampleRequest {
'value1': string;
'value2'?: number;
}
クライアントでは以下のように使用します。
import axios, { AxiosError } from "axios";
import { ExampleControllerApiFactory } from '@/generated';
import type { ExampleRequest } from '@/generated';
import type { ExampleResponse } from '@/generated';
const client = axios.create({
baseURL: 'http://localhost:8080',
timeout: 30000,
withCredentials: false,
});
const request: ExampleRequest = {
value1: 'aaaaa',
value2: 99999
}
const api = ExampleControllerApiFactory(undefined, undefined, client);
const response = await api.post({ exampleRequest: request });
Discussion