前回まででReactにより画面を作成したので、次はいよいよフロント側からサーバ側のAPIを呼び出す処理を書いてみる。ようやくWebアプリっぽくなってきた気がする(?)
サーバ側のAPI作成
API追加
models/UserModel.ts
まずはデータのやり取りをするのにModelを作成する。
サンプルとしてユーザー情報の管理を行うコードを書くので、UserModelを作成する。
export default interface UserModel {
id: number
account: string
password?: string
}
実際のシステムならDB等で管理するが、とりあえずAPIの動作確認をするので、サンプルデータを変数で管理する。(サーバを再起動するとデータは初期化されるよ)
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を追加する。
import UserModel, { testUserList } from 'models/UserModel';
ユーザー一覧のAPIを追加する。
router.get('/list', function (req, res, next) {
res.json(testUserList)
})
get
の第一引数にはAPIのパス"/list"
を指定する。ここでは単純にユーザーの一覧を返すので、json()
メソッドにデータの一覧を渡す。これで、レスポンスとしてJSON形式のデータを送信することができる。
同様に、ユーザーの詳細を返すAPIを作成する。こんどはアクセスするURLを'/detail/:id'
にする。ここで、:id
の箇所はユーザーIDを指定するいわゆるパスパラメーター(もしくはURLパラメータ)等と呼ばれる仕組みを利用する。
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も作成する。
クエリーパラメータからデータを取得し、テストデータに追加する。
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.use(function (req, res, next) {
res.setHeader('cache-control', 'no-store')
return next()
})
APIのパスを/api/users
以下に修正する。
//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
続いて、以下のURLを開いてユーザーを追加してみる。
http://localhost:4000/api/users/create?account=test&password=fuga
ユーザー追加後に再度一覧を開いて、ユーザーが追加されていれば成功である。
http://localhost:4000/api/users/list
画面からのAPI呼び出し
次は、APIを呼び出す処理を画面側に追加する。
API呼び出し処理
vite.config.ts
画面からAPIを呼び出す場合、開発サーバーはAPIへのリクエストを別に処理する必要がある。そこで、vite.config.ts
に以下のようにproxy
の設定を追加することにより、APIへのリクエストを他のポートやサーバに転送することができる。
ここではバックエンドのサーバをhttp://localhost:4000
で起動しているので、/api
以下のリクエストをhttp://localhost:4000
へ転送するよう設定している。
ついでに、ソースコードを修正したらリロードするようusePollingを設定しておく。
server: {
watch: {
usePolling: true
},
proxy: {
'/api': 'http://localhost:4000',
}
}
pages/UsersListPage.tsx
ユーザー一覧画面を修正し、以下のコードを追加する。
importにuseEffect
とuseState
を追加する。
import React, { MouseEvent, useCallback, useEffect, useState } from 'react'
APIを呼び出した結果を保存し再度APIを呼び出さないように、useState
で取得した変数に保存する。
//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の状態管理について書くと長くなるので、ここではこれ以上の説明は省略)
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を何度も呼び出さないため必要である。
useEffect(() => {
loadUserList()
}, [loadUserList])
pages/UsersDetailPage.tsx
同様に、詳細画面にもAPI呼び出しのコードを追加する。
既存のコードを修正し、useState
から取得した値でuser
とsetUser
を定義する。
// 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部分を取得し該当するユーザー情報を返している。
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にアクセスする。
サーバ側のユーザーデータを修正して画面を再表示したら、修正後のデータが表示されれば成功である。
APIを叩いてユーザーを追加し、ユーザー一覧画面を再表示すれば一覧にも反映される。
http://localhost:4000/api/users/create?account=test&password=fuga
以上でユーザー情報取得APIは動作するようになったが、ユーザー追加・削除はまだ実装していないので、次回に説明する。