React+TypeScriptでOpenAPIを使ってRESTで通信
お仕事でAndroid+kotlinサーバサイドでOpenAPIを使ってREST通信のアプリを開発していたのですが、ひょんなことからWebアプリも開発することになりました。
ここ10数年くらいはサーバサイドばかりでフロントエンドやってなかったのですが、折角ならReact+TypeScrptで作ってみようと思い立ちました。
サーバサイドは今までと同じように、kotlin + sprintBoot3 + OpenAPIです。
OpenAPIのインタフェース(json、yaml)の作り方は変わりません。上記の記事の②の方法、サーバサイドの実装から作ります。
この時に1点注意しなければならないのは、サーバサイド側のControllerクラスのアノテーションの付け方で、
@RestController
class MyController(private val service: MyService) {
@Tag(name = "mySearch")
@Operation(
・・・・
)
@PostMapping(・・・)
fun mySearch(@RequestBody request: MyRequest): ResponseEntity<MyResponse> {
@Tagアノテーションを付けないと、React側のTyepScriptのスタブのクラス名がControllerのクラス名になってしまいます。Controllerのクラス名が長い場合は@Tagアノテーションで指定してやります。@TagはそのControllerクラスの全てのメソッドで統一したほうがいいです。Controllerクラスを跨いで重複はできません。
React側でやること
Reactのプロジェクトを作成する
npxでTypeScript付でReactのプロジェクトを作ります。
npx create-react-app {プロジェクト名} --template typescript
OpenAPI Generatorのinstall
OpenAPI Generatorをinstallします。今回はHTTPクライアントライブラリにaxiosを使いますのでaxiosもinstallします。
cd {プロジェクト名}
npm install @openapitools/openapi-generator-cli -g
npm install axios
OpenAPIのスタブの生成
OpenAPIのインタフェース定義(json、またはyaml)はサーバサイドで生成して、Reactの任意のフォルダにコピーしておきます。上記の記事を参考にしてください。ここでは仮に、
{プロジェクトRoot}/apiDoc/MyApplication.json
に置いたと仮定します。OpenAPIのスタブを生成するには、以下のように実行します。
npx @openapitools/openapi-generator-cli generate -i .\apiDocs\MyApplication.json -g typescript-axios -o src/openapi-generated
- -i でOpenAPIのインタフェース定義(json、またはyaml)のパスを指定
- -o で生成したスタブの出力先のフォルダを指定します
- -g で生成するスタブのタイプを指定します。
この生成するスタブのタイプというのが、OpenAPI Generatorの公式ページを見ると、TypeScriptだけでもこれだけあります。
- typescript (experimental)
- typescript-angular
- typescript-aurelia
- typescript-axios
- typescript-fetch
- typescript-inversify
- typescript-jquery
- typescript-nestjs (experimental)
- typescript-node
- typescript-redux-query
- typescript-rxjs
ぱっと見、どれにしたらいいのかわかりませんね。色々調べてChatGPTにも聞いてみた結果、axiosが良さそうということで、typescript-axiosにしました。
typescript-axiosの場合は上記のようにnpmでaxiosのinstallが必要になります。
スタブの出力先のフォルダには以下のようなファイルができているはずです。
OpenAPIのインタフェース定義の内容はほとんどがapi.tsで網羅されています。
OpenAPIインタフェースを非同期RESTで呼び出す。
さて、OpenAPIインタフェースを非同期RESTで呼び出すには
import {
Configuration,
MySearchApi,
MySearchRequest,
MySearchResponse
} from '../../openapi-generated';
const config= new Configuration({
basePath: process.env.REACT_APP_API_BASE_PATH,
})
const myClient = new MySearchApi(config);
configのbasePathは.envから取得します。MySearchApiをconfigを引数にnewします。
RESTを呼び出す関数は、非同期関数で、以下の様になります
export async function mySearch(searchText: string): Promise<sting> {
const request: MySearchRequest = {
searchText: searchText,
}
return myClient.mySearch(request)
.then((response) => {
console.log(response.status);
return response.data.seachResult;
})
.catch((error) => {
console.log(`error=${error}`);
throw error;
});
}
そして、この非同期関数を呼び出す側の関数は以下の様になります。
try{
const result = await mySearch(searchText);
// 何らかの処理
} catch(error: unknown) {
const axiosError = error as AxiosError;
if (axiosError.response && axiosError.response.status === 400) {
console.log('見つかりませんでした');
} else {
console.log('エラーが発生しました。');
}
}
React + TypeScriptでOpenAPIを使って見た感想
他でも同じですが、OpenAPIを使えばサーバサイドとクライアントでインタフェースのズレによる不可解な事象というのは起こりません。
また、REST通信は非同期なので、非同期関数(async)とそれを呼び出す(await)のふたつの関数が必要になりますが、独自で非同期通信を実装するよりは簡単だと思います。
サーバサイドが同じで、クライアントがAndroid(kotlin)、Web(JavaScript、TypeScript)、Java、go、PHP、Python・・・等々、色々な言語に対応できます。
是非、React + TypeScriptでOpenAPIを使って見てください。