前回までで画面からAPIを呼び出す処理を作成したが、RESTに準拠したAPIに修正する。
シリーズ記事一覧
RESTに準拠したAPIの作成
前回はブラウザからAPIを動作確認しやすいようにget
メソッドに応答するAPIを作成してきたが、RESTではCRUDの処理に合わせたメソッドを使用したほうが良いとされているので、パスやメソッドをRESTっぽく変えてみる。
(RESTに厳密に準拠すると説明が長くなるので部分的に修正する)
API修正
まずはユーザー一覧と詳細のAPIを追加する。
users.ts
以下のコードをusers.ts
に追加する。
APIのパスは/api/users
と/api/users/:id
と短くしている。
また、パスワードをAPIで送信しないようgetUserOutput
で変換している。
function getUserOutput(user: UserModel): UserModel {
const output = { id: user.id, account: user.account } as UserModel
return output
}
router.get('/', function (req, res, next) {
const destList = testUserList.map(user => getUserOutput(user))
res.json(destList)
})
router.get('/: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
}
const dest = getUserOutput(user)
res.json(dest)
})
同様に、ユーザー作成APIを追加する。
データの作成や更新を行う場合はpost
やput
メソッドを使用し、データ取得系のAPIと区別しやすくするのが一般的である。
routes.post()
を使用するとpost
メソッドで応答するAPIとなる。
router.post('/', function (req, res, next) {
const user = req.body as UserModel
const oldId = testUserList.map(p => p.id).reduce((a, b) => Math.max(a, b), 0)
user.id = oldId + 1
testUserList.push(user)
const dest = getUserOutput(user)
res.json(dest);
});
更に、ユーザー更新と削除APIを追加する。
メソッドはput
とdelete
にしている。
メソッドが異なる場合は同じパスを使用できるので、/api/users/:id
と/api/users
の短いパスにしている。
router.put('/:id', function (req, res, next) {
const id = parseInt(req.params.id)
const userInput = req.body as UserModel
const user = testUserList.find(p => p.id === id)
if (user === undefined) {
res.sendStatus(404)
return
}
user.account = userInput.account
if (userInput.password !== undefined && userInput.password.length > 0) {
user.password = userInput.password
}
res.json({ success: true });
});
router.delete('/:id', function (req, res, next) {
const id = parseInt(req.params.id)
const index = testUserList.findIndex(p => p.id === id)
if (index < 0) {
res.sendStatus(404)
return
}
testUserList.splice(index, 1)
res.json({ success: true });
});
ここまででサーバ側のAPIの追加は完了である。次はAPIを呼び出す画面側を修正する。
画面修正
ユーザー一覧画面とユーザー詳細画面を修正し、ユーザーの追加・削除も行えるようにする。
main.tsx
ユーザー詳細画面をユーザー登録にも流用するため、/users/new
のパスで画面のRoute
を追加する。注意点として、Route
のパスは上から順に判定されるので、/users/new
を上に追加しないと/users/new
のパスに一致しなくなる。
<Route path="/users/new" element={<UsersDetailPage />} />
<Route path="/users/:id" element={<UsersDetailPage />} />
UsersListPage.tsx
ユーザー一覧APIのURLを修正する。
// const apiPath = "/api/users/list"
const apiPath = "/api/users"
createボタンが押されたらユーザー詳細画面に遷移するよう修正。
const onClickUserCreateLink = useCallback((e: MouseEvent) => {
e.preventDefault()
// alert("create!!")
navigate("/users/new")
}, [navigate])
deleteボタンが押されたら削除APIを呼び出すよう修正。
fetch
メソッドは、RequestInit.method
でHTTPメソッドを指定して呼び出すことができる。
ここでは削除処理なのでdelete
メソッドを指定している。
削除処理が完了したらloadUserList
を呼び出してユーザー一覧を更新している。
const onDelete = useCallback(async (user: UserModel) => {
if (!confirm(`delete user [${user.account}]?`)) {
return
}
const req = {
method: "delete"
} as RequestInit
const res = await fetch("/api/users/" + user.id, req)
const data = await res.json()
if (data.success) {
alert("delete complete");
}
loadUserList()
}, [loadUserList])
const onClickUserDelete = useCallback((e: MouseEvent, user: UserModel) => {
e.preventDefault()
// alert(`${user.account} delete!!`)
onDelete(user)
}, [onDelete])
UsersDetailPage.tsx
ユーザーの登録・更新に対応する。
ユーザー登録の場合はユーザーIDがundefined
になるので、判別できるよう-1
を設定しておく。
// const userId = parseInt(params["id"]!!)
const userId = parseInt(params["id"] ?? "-1")
ユーザー詳細APIのパスを修正する。
// const apiPath = "/api/users/detail/" + user.id
const apiPath = "/api/users/" + user.id
ユーザー登録APIの呼び出し処理を追加する。
ユーザー登録APIはpost
メソッドで呼び出す。
ヘッダにContent-Type
を指定してJSON形式で送信することを指定する。
送信するデータはJSON形式の文字列にしてbody
に指定する。
ユーザー登録後はAPIのレスポンスからユーザー情報を取得し画面を更新している。
const createUser = useCallback(async () => {
const req = {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
} as RequestInit
const res = await fetch("/api/users", req)
if (res.status >= 400) {
alert("save error")
return
}
const data = await res.json() as UserModel
setUser(data)
alert("save complete!!")
}, [user, setUser])
同様に、ユーザー更新APIの呼び出し処理を追加する。
ユーザー更新APIはput
メソッドで呼び出している。
更新後はユーザー一覧画面に遷移している。
(※登録・更新後に画面の表示を更新する場合と、一覧画面に戻るシステムのどちらもありうるので、ここでは更新後は一覧画面に戻る処理にしている)
const updateUser = useCallback(async () => {
const req = {
method: "put",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
} as RequestInit
const res = await fetch("/api/users/" + user.id, req)
if (res.status >= 400) {
alert("save error")
return
}
navigate("/users")
}, [user, navigate])
保存ボタンを押された時の処理を修正し、新規登録の場合(user.id === -1)なら登録APIを、それ以外なら更新APIを呼び出す。
const onSave = useCallback((e: MouseEvent) => {
e.preventDefault()
// alert("save!!")
if (user.id === -1) {
createUser()
}
else {
updateUser()
}
}, [createUser, updateUser, user])
入力が更新された場合の処理を追加する。
Reactでは随時画面が更新されるので、入力内容は状態変数に保存しておく。
{ ...user, account: e.target.value }
の箇所は変数の要素を展開するスプレッド構文を利用して変数のコピーを作成しているもので、user
変数のコピーにaccount
プロパティを設定している。
setUser
に指定する変数は更新したことを知らせるためuser
とは別の変数を指定する必要があり、このような処理が必要になる。
const onChangeAccount = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const newUser = { ...user, account: e.target.value }
setUser(newUser)
}, [user, setUser])
const onChangePassword = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const newUser = { ...user, password: e.target.value }
setUser(newUser)
}, [user, setUser])
入力欄を修正する。
入力内容が変更されるとonChangeが呼ばれるので、そこで変数に値を保存している。
<input type="text" value={user.account} onChange={onChangeAccount} autoComplete="off" />
<input type="password" value={user.password} onChange={onChangePassword} autoComplete="new-password" />
動作確認
フロントとサーバの両方を起動しユーザー一覧画面を表示する。
http://localhost:3000/users
ユーザーの追加・削除・更新が行えれば成功である。
2回に分けてもだいぶ長い記事になってしまったが、これでフロント・サーバでのAPIの実装について解説できたのではないかと思う。