React+TypeScriptでOpenAPIを使う

# React+TypeScriptでOpenAPIを使ってRESTで通信

お仕事でAndroid＋kotlinサーバサイドでOpenAPIを使ってREST通信のアプリを開発していたのですが、ひょんなことからWebアプリも開発することになりました。
ここ10数年くらいはサーバサイドばかりでフロントエンドやってなかったのですが、折角ならReact＋TypeScrptで作ってみようと思い立ちました。

サーバサイドは今までと同じように、kotlin + sprintBoot3 + OpenAPIです。

https://qiita.com/cozyk100/items/c2afb1885f0dbabb0be0

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の公式ページ](https://openapi-generator.tech/docs/generators/)を見ると、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が必要になります。

スタブの出力先のフォルダには以下のようなファイルができているはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/3359606a-debf-52ed-0e40-0b690f5e8cfd.png)

OpenAPIのインタフェース定義の内容はほとんどがapi.tsで網羅されています。

## OpenAPIインタフェースを非同期RESTで呼び出す。

さて、OpenAPIインタフェースを非同期RESTで呼び出すには
```TypeScript
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を呼び出す関数は、非同期関数で、以下の様になります
```TypeScript
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;
    });
}
```
そして、この非同期関数を呼び出す側の関数は以下の様になります。
```TypeScript
    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を使って見てください。