Stoplight StudioとOpenAPI Generatorを使ってみる
Nuxt(SPA)とLaravelのプロジェクトで、Stoplight StudioとOpenAPI Generatorを導入してみました。スキーマを定義してフロントエンドの型を生成するまでのメモを書きたいと思います。
検証用のリポジトリはこちらです。
環境
- @openapitools/openapi-generator-cli@2.5.1
Stoplight Studioとは
Stoplight Studioは、OpenAPIの定義ファイルをGUIで作成できるツールです。定義したエンドポイントにPostmanのようにリクエストを送ったり、組み込みのモックサーバーを使ってダミーのAPIを作ることができます。
APIの定義
ディレクトリを作成し、作成したディレクトリをStoplight Studioで開きます。モノレポにしているため、プロジェクトルートにapispec
というディレクトリを作りました。
1点注意が必要なのは、openapi-generatorがOAS3.1にまだ対応していないことです。そのため、Stoplight Studioでバージョンを選択するときは、3.0を選択します。
openapi-generatorが使用しているswagger-parserが最近OAS3.1に対応したみたいなので、これから3.1対応が進みそうな気配があります。
Stoplight Studioでは、エンドポイントとリクエスト、レスポンスを定義します。このあたりはGUIをポチポチするだけでした。今回はタスクの詳細と編集のAPIスキーマを定義してみました。
スキーマ定義
openapi: 3.0.0
x-stoplight:
id: dmiy7jxs5yhc4
info:
title: todo-app
version: "1.0"
servers:
- url: "http://localhost:3100"
paths:
"/tasks/{taskId}":
parameters:
- schema:
type: string
name: taskId
in: path
required: true
get:
summary: タスク詳細を取得する
tags:
- tasks
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
description:
type: string
nullable: true
status:
type: string
status_id:
type: integer
minimum: 1
maximum: 5
required:
- id
- name
- description
- status
- status_id
operationId: get-tasks-taskId
put:
summary: タスクを更新する
tags:
- tasks
responses:
"204":
description: No Content
operationId: put-tasks-taskId
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
description:
type: string
status_id:
type: number
required:
- name
- status_id
components:
schemas: {}
型の自動生成
openapi-generatorをインストールします。yarnでインストールしました。
yarn add -D @openapitools/openapi-generator-cli
以下のコマンドで、APIクライアントと型定義を生成します。
openapi-generator-cli generate -i ../apispec/reference/todo-app.yaml -g typescript-fetch -o api -c ./openapi-generator.json
必須パラメータはAPI定義ファイルのパス(-i
)と、ジェネレーターのテンプレート(-g
)です。型だけを使用したかったので、テンプレートはシンプルそうなtypescript-fetchを選択しました。その他、出力ディレクトリと設定ファイルを指定しています。
パラメータ名 | 説明 |
---|---|
-i | API定義ファイルのパス |
-g | ジェネレーターのテンプレート |
-o | 出力ディレクトリ |
-c | 設定ファイルのパス |
設定ファイルにはプロパティ名をスネークケースにする設定を書きました。
{
"modelPropertyNaming": "snake_case"
}
コマンドを実行すると、以下のようなファイルが作られます。
api
├── apis
│ ├── TasksApi.ts
│ └── index.ts
├── index.ts
├── models
│ ├── GetTasksTaskId200Response.ts
│ ├── PutTasksTaskIdRequest.ts
│ └── index.ts
└── runtime.ts
apis/
の中身がAPIクライアントです(今回は使用しません)。models/
の中に定義したリクエストやレスポンスの型が含まれています。modelには以下のような型定義が書かれています。
// タスク詳細APIのレスポンスボディの型
export interface GetTasksTaskId200Response {
id: number;
name: string;
description: string | null;
status: string;
status_id: number;
}
// タスク編集APIのリクエストボディの型
export interface PutTasksTaskIdRequest {
name: string;
description?: string;
status_id: number;
}
リクエストからフォームの型に変換する
生成された型を、フォームに合わせた型に変換する型をつくります。フォームの初期値はnullにしたかったので、プロパティにnull型を加えています。
// TODO: object以外のいい感じの型を使う
export type RequestToForm<T extends object> = {
[K in keyof T]-?: T[K] extends object ? RequestToForm<T[K]> : T[K] | null
}
-?
で省略可能を外しているのは、(作っていたアプリが業務システムなのもあり)省略可能なプロパティがたくさんあったため、フロントエンドで代入し忘れないようにするためです。
form.value = dataFromApi // refにまるっと代入するとき、フィールドが省略可能だと渡し忘れに気づけない
コンポーネントでは以下のように使います。
// テンプレート部分は省略
import axios from "axios";
import { reactive } from "vue";
import { GetTasksTaskId200Response, PutTasksTaskIdRequest } from "./api";
import { RequestToForm } from "./utils/form";
const taskId = 1;
const endpoint = `http://localhost:3100/tasks/${taskId}`;
// リクエストボディの型から、フォームの型を作成する
type EditTaskForm = RequestToForm<PutTasksTaskIdRequest>;
// フォームを初期化する
const editForm = reactive<EditTaskForm>({
name: null,
description: null,
status_id: null,
});
// APIからデータを取得する
axios.get<GetTasksTaskId200Response>(endpoint).then((response) => {
editForm.name = response.data.name;
editForm.description = response.data.description;
editForm.status_id = response.data.status_id;
});
// フォームを送信する
const onSubmit = () => {
axios.put(endpoint, editForm).then(() => {
alert("タスクを更新しました");
});
};
// テンプレート部分は省略
まとめ
- OpenAPI GeneratorがOAS3.1に未対応のため、Stoplight Studioでは3.0を使用する
- openapi-generatorを使うと、定義ファイルからTypeScriptの型定義を自動生成できる
- 自動生成したAPIの型をフォーム用に変換する型を書けばいいかもしれない
モックサーバーを使用したテストや、API定義とAPIを一致させるための仕組みはまだできていないので、今後実践していければと思います。GraphQLがうらやましい
Discussion