0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

前回まででReactにより画面を作成したので、次はいよいよフロント側からサーバ側のAPIを呼び出す処理を書いてみる。ようやくWebアプリっぽくなってきた気がする(?)

サーバ側の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を作成する。

ここでは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の箇所にあたる123req.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);

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

動作確認

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

cd back
pnpm debug

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

image.png

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

image.png

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

image.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にuseEffectuseStateを追加する。

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で保存しておく。

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は(変数が更新されなければ)画面表示時に1度呼ばれる処理なので、APIを何度も呼び出さないため必要である。

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

pages/UsersDetailPage.tsx

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

既存のコードを修正し、useStateから取得した値でusersetUserを定義する。

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にアクセスする。
サーバ側のユーザーデータを修正して画面を再表示したら、修正後のデータが表示されれば成功である。

image.png

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


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

サンプルコード

https://github.com/betarium/sample-react-express/tree/0.4.0

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?