ReactからExpress側のWebAPIを呼び出してみた（その1）

[前回](https://qiita.com/betarium/items/fe5d3be5155de405c3d6)まででReactにより画面を作成したので、次はいよいよフロント側からサーバ側のAPIを呼び出す処理を書いてみる。ようやくWebアプリっぽくなってきた気がする（？）

:::note
[前回：Reactで画面を実装してみた](https://qiita.com/betarium/items/fe5d3be5155de405c3d6)
:::

https://qiita.com/betarium/items/fe5d3be5155de405c3d6

# サーバ側のAPI作成

## API追加

### models/UserModel.ts
まずはデータのやり取りをするのにModelを作成する。
サンプルとしてユーザー情報の管理を行うコードを書くので、UserModelを作成する。

```models/UserModel.ts
export default interface UserModel {
    id: number
    account: string
    password?: string
}
```

実際のシステムならDB等で管理するが、とりあえずAPIの動作確認をするので、サンプルデータを変数で管理する。（サーバを再起動するとデータは初期化されるよ）

```models/UserModel.ts
const user1 = { id: 1, account: "admin", password: "hogehoge" } as UserModel
const user2 = { id: 2, account: "guest", password: "piyopiyo" } as UserModel

export const testUserList = [user1, user2]
```

### users.ts
users.tsには生成されたサンプルコードがあったが、これを修正する。
まずはユーザー一覧を返すAPIを作成する。

:::note warn
ここではAPIの動作を見るため特にアクセス制限を行わずパスワードも含めてそのまま送信しているが、実際のシステムではアクセス制限や不要なデータを除外するなどの処理が必要である。
:::

モデルを参照するためimportを追加する。

```users.ts
import UserModel, { testUserList } from 'models/UserModel';
```

ユーザー一覧のAPIを追加する。

```users.ts
router.get('/list', function (req, res, next) {
  res.json(testUserList)
})
```

```get```の第一引数にはAPIのパス```"/list"```を指定する。ここでは単純にユーザーの一覧を返すので、```json()```メソッドにデータの一覧を渡す。これで、レスポンスとしてJSON形式のデータを送信することができる。

同様に、ユーザーの詳細を返すAPIを作成する。こんどはアクセスするURLを```'/detail/:id'```にする。ここで、```:id```の箇所はユーザーIDを指定するいわゆるパスパラメーター（もしくはURLパラメータ）等と呼ばれる仕組みを利用する。

```users.ts
router.get('/detail/:id', function (req, res, next) {
  const id = parseInt(req.params.id)

  const user = testUserList.find(p => p.id === id)

  if (user === undefined) {
    res.sendStatus(404)
    return
  }

  res.json(user)
})
```

パスパラメータを使用している場合、URLで```/detail/123```とアクセスすると、```/detail/:id```の```:id```の箇所にあたる```123```が```req.params.id```で取得できる。
取得した値はstring型なので、```parseInt```でnumber型に変換する。

idをもとにユーザーの情報を取得し、データがなければ```res.sendStatus(404)```で404エラーを返す。

データが存在する場合は```json()```でユーザーデータを返す。

ユーザーを追加するAPIも作成する。
クエリーパラメータからデータを取得し、テストデータに追加する。
```users.ts
router.get('/create', function (req, res, next) {
  const account = req.query.account
  const password = req.query.password

  const oldId = testUserList.map(p => p.id).reduce((a, b) => Math.max(a, b), 0)

  const id = oldId + 1
  const user = { id, account, password } as UserModel

  testUserList.push(user)

  res.json(user)
});

```

### app.ts

レスポンスがキャッシュされないようにする。
これを忘れるとブラウザをF5で更新しないと表示されるデータが変わらなかったり、nginxを使用した場合にキャッシュされて悲惨な目に合うので注意。

```app.ts
app.use(function (req, res, next) {
  res.setHeader('cache-control', 'no-store')
  return next()
})
```

APIのパスを```/api/users```以下に修正する。

```app.ts
//app.use('/users', usersRouter);
app.use('/api/users', usersRouter);
```

:::note
実際のAPIのURLはここで指定したパスに`users.ts`で指定したパスが追加されたパスとなる。
例えば、ユーザー詳細APIのパスは`/api/users`+`/detail/:id`で`/api/users/detail/:id`となる。
:::

## 動作確認

ここまで作成したら、サーバを起動してAPIをたたいてみる。

```sh
cd back
pnpm debug
```

ブラウザから以下のURLを開いて、ユーザー一覧と詳細データが表示されれば成功である。
http://localhost:4000/api/users/list
http://localhost:4000/api/users/detail/1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/df355076-803c-59f5-604d-09fb6f24696e.png)

続いて、以下のURLを開いてユーザーを追加してみる。
http://localhost:4000/api/users/create?account=test&password=fuga

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/551be017-671e-516f-5da6-3fdcb528de46.png)

ユーザー追加後に再度一覧を開いて、ユーザーが追加されていれば成功である。
http://localhost:4000/api/users/list

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/2c0c880b-638a-198d-1a05-09272e7137f6.png)

# 画面からのAPI呼び出し

次は、APIを呼び出す処理を画面側に追加する。

## API呼び出し処理

### vite.config.ts

画面からAPIを呼び出す場合、開発サーバーはAPIへのリクエストを別に処理する必要がある。そこで、`vite.config.ts`に以下のように`proxy`の設定を追加することにより、APIへのリクエストを他のポートやサーバに転送することができる。
ここではバックエンドのサーバを`http://localhost:4000`で起動しているので、`/api`以下のリクエストを`http://localhost:4000`へ転送するよう設定している。
ついでに、ソースコードを修正したらリロードするようusePollingを設定しておく。

```vite.config.ts
  server: {
    watch: {
      usePolling: true
    },
    proxy: {
      '/api': 'http://localhost:4000',
    }
  }
```

### pages/UsersListPage.tsx

ユーザー一覧画面を修正し、以下のコードを追加する。

importに`useEffect`と`useState`を追加する。
```pages/UsersListPage.tsx
import React, { MouseEvent, useCallback, useEffect, useState } from 'react'
```

APIを呼び出した結果を保存し再度APIを呼び出さないように、`useState`で取得した変数に保存する。

```pages/UsersListPage.tsx
  //const userList = testUserList

  const [userList, setUserList] = useState<UserModel[]>([])
```

API呼び出しの関数を追加する。
APIの呼び出しは非同期に実行しないと他の処理が止まってしまうので、`async`で非同期関数として定義する。
APIの呼び出しは`fetch`メソッドを使用する。
`fetch`メソッドの結果としてレスポンスデータを持つ`Response`オブジェクトが返る。`Response.json()`メソッドでレスポンスのボディをJSON形式で取得できる。
ここではユーザー一覧の配列をAPIで送信しているので、`as UserModel[]`でキャストする。
取得した配列は`setUserList`で保存しておく。

:::note
Reactの状態管理について
`useState`は状態変数と、状態変数を更新するための関数を配列で返す。`useState`で取得した関数に値を設定すると、自動で画面が再描画される。
画面の再描画時には状態変数にAPIから取得したデータを設定しているので、取得したデータを画面に表示できるという仕組みである。
（※Reactの状態管理について書くと長くなるので、ここではこれ以上の説明は省略）
:::

```pages/UsersListPage.tsx
  const loadUserList = useCallback(async () => {
    const apiPath = "/api/users/list"
    const res = await fetch(apiPath)
    const data = await res.json() as UserModel[]
    setUserList(data)
  }, [setUserList])
```

`useEffect`から`loadUserList`を呼び出す。
`useEffect`は（変数が更新されなければ）画面表示時に１度呼ばれる処理なので、APIを何度も呼び出さないため必要である。

```pages/UsersListPage.tsx
  useEffect(() => {
    loadUserList()
  }, [loadUserList])
```

### pages/UsersDetailPage.tsx

同様に、詳細画面にもAPI呼び出しのコードを追加する。

既存のコードを修正し、`useState`から取得した値で`user`と`setUser`を定義する。
```pages/UsersDetailPage.tsx
  // const user = testUserList.filter(p => p.id === userId)[0]

  const [user, setUser] = useState<UserModel>({ id: userId, account: "", password: "" } as UserModel)
```

API呼び出しの処理を`loadUserDetail`に追加し、`useEffect`から呼び出すようにする。
APIのパスには`/api/users/detail/123`のようにユーザーIDを含めたパスを送信し、パスパラメータの機能を使用してサーバ側でID部分を取得し該当するユーザー情報を返している。

```pages/UsersDetailPage.tsx
  const loadUserDetail = useCallback(async () => {
    const apiPath = "/api/users/detail/" + user.id
    const res = await fetch(apiPath)
    const data = await res.json() as UserModel
    setUser(data)
  }, [setUser])

  useEffect(() => {
    loadUserDetail()
  }, [loadUserDetail])
```

## 動作確認


以上の修正が完了したら、フロントとサーバの両方を起動し以下のURLにアクセスする。
サーバ側のユーザーデータを修正して画面を再表示したら、修正後のデータが表示されれば成功である。

http://localhost:3000/users

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/676ff248-1a1f-b46e-4de5-f7b0bd0c2163.png)

APIを叩いてユーザーを追加し、ユーザー一覧画面を再表示すれば一覧にも反映される。
http://localhost:4000/api/users/create?account=test&password=fuga

----

以上でユーザー情報取得APIは動作するようになったが、ユーザー追加・削除はまだ実装していないので、次回に説明する。

# サンプルコード
[https://github.com/betarium/sample-react-express/tree/0.4.0](https://github.com/betarium/sample-react-express/tree/0.4.0)