第24話 APIサーバの構築(POST編)
こんにちは!Kenです。
今日は第21話とともに管理画面作りの肝となるAPIサーバ構築(POST編)を書いていきたいと思います。
CRUD処理とは・・・なんぞや
勘の良い方はすでに気付かれたのではないでしょうか。。。?
お前のやりたいことは、施術時間データの更新だろと。更新ならPUTちゃうんかい!と。
はい、私も当初そう思ってました。しかし進めるうちに驚愕の事実に気付いてしまったのです。
ところで、CRUD処理という言葉はもうご存知と思います。
C→Create
R→Read
U→Update
D→Delete
これらHTTPメソッドにおいては、次のように当てはまるのです。
C→Create→POST
R→Read→GET
U→Update→PUT
D→Delete→DELETE
なんでDELETEだけ一致してるんや!ということはともかく。
私はHTMLのフォーム(施術時間とかが入力されてるinput)をサーバー側に送るコーディングを書いていて、壁にぶち当たりました。formのmethodをputにしてもどうしてもうまくいかないのです。getやpostならうまく行くのに・・・
調べていくと、なんとHTMLはPUTとDELETEをサポートしていないらしいのです。結構古い記事ですが、今もおそらく対応してないと思います。
実際やってみて分かったのですが、PUTとDELETEの代わりは全部POSTで何とかなるというものです。知っている人は知ってるのかもしれませんが・・・この辺の詳細分かる人はぜひ教えていただきたいです。
ということで、POSTにて進めていきたいと思います。
編集ボタンの実装
前回、編集ボタンの中身は何も実装していませんでした。中身をコーディングしていきましょう。
//編集ボタンクリック時の動作
editButton.addEventListener('click',()=>{
//formのactionを設定 paramとしてidをつける
formElement.setAttribute('action',`api/users/${userDataArray[0]}`);
//各インプットの入力をできるようにする
input_name.disabled = false;
input_cut.disabled = false;
input_shampoo.disabled = false;
input_color.disabled = false;
input_spa.disabled = false;
//送信ボタンの生成
const sendButton = document.createElement('input');
sendButton.value = '送信';
sendButton.type = 'button';
sendButton.setAttribute('class','btn btn-warning card-button');
//sendButtonクリック時の処理
sendButton.addEventListener('click',(e)=>{
e.preventDefault();
if(!isNaN(document.userInfo.cuttime.value) && !isNaN(document.userInfo.shampootime.value) && !isNaN(document.userInfo.colortime.value) && !isNaN(document.userInfo.spatime.value)){
const data = new FormData(formElement);
console.log('FormData:',...data.entries());
fetch(`/api/users/${userDataArray[0]}`,{
method:'POST',
body:data,
creadentials:'same-origin'
})
.then(response=>{
console.log('response:',response);
})
.catch(e=>{
throw e;
});
}else{
alert('時間は半角数値を入力してください。');
}
});
divButton.appendChild(sendButton);
//編集ボタンと削除ボタンを消す
deleteButton.style.display = 'none';
editButton.style.display = 'none';
});
divButton.appendChild(editButton);
editButtonの役割は、
・各インプットのdisabledを解除し、入力可能な状態にする
・入力データをサーバへPOSTするsendButtonを生成
・editButton,deleteButtonを消去する
そして新たに生成したsendButtonの役割は、
・各インプットに入力されたものが半角数値であることをチェック
・半角数値であれば、fetchで/api/users/idへPOST
/api/usersの後ろについている/idはusersData[0]のことで、対象顧客のidを表している。このようにidをつけて送ることで、誰のデータを更新するのかをサーバ側で判別できるようにしている。
では/api/users/idへのPOST処理をコーディングしていこう。まずはルーティングからだ。
ルーティングの設定
ルーティングは以前と同じように/routers/api.jsの中にコーディングすればOKだ。
const express = require('express');
const router = express.Router();
const controller = require('../controllers/api');
router
.route('/')
.get(controller.getData);
router
.route('/users/:id')
.post(controller.putUser);
module.exports = router;
postを検知したらcontrollerのputUserなるメソッドを実行するルーティング設定だ。users/の後についている:idは顧客IDがパラメータとしてついてきていることを意味する。これはコントローラーの中でパラメータとして取得することができる。
コントローラーの実装
では/controllers/api.jsの中に新たなコントローラーを実装していこう。
module.exportsの中に以下コードを追加する
putUser: (req,res) => {
const id = parseInt(req.params.id);
const {name,cuttime,shampootime,colortime,spatime} = req.body;
try{
Data.updateUser({id,name,cuttime,shampootime,colortime,spatime})
.then(message=>{
console.log('message:',message);
res.status(200).send(message);
})
.catch(e=>console.log(e));
}catch(error){
res.status(400).json({message:error.message});
}
}
idはrouterのところで、/:idとしたものです。req.params.idとして取得することができます。
そして{name,cuttime,shampootime,colortime,spatime}をreq.bodyから取得しています。今のままだと、これが実行できないので、必要なパッケージをインストールし、読み込みます。
まず、connect-multipartyをインストールします。ターミナルで、以下です。
$ npm i --save connect-multiparty
大元のindex.jsファイルへ飛びます。プログラム冒頭で、パッケージを読み込みます。
const multipart = require('connect-multiparty');
そしてexpressの部分です。multipartをuseします。
app
.use(express.static(path.join(__dirname,'public')))
.use(multipart()) //connect-multiparty
.use('/',router)
.use('/api',apiRouter)
.post('/hook',line.middleware(config),(req,res)=> lineBot(req,res))
.set('views', path.join(__dirname, 'views'))
.set('view engine', 'ejs')
.listen(PORT,()=>console.log(`Listening on ${PORT}`));
ここで注意点ですが、multipart()はline.middlewareよりも上でuseしてあげる必要があります。line.middlewareよりも下でuseした場合、LINEBOTが使えなくなってしまいましたので。
このようにconnect-multipartyを使用することで、リクエストヘッダーのContent-Typeがmultipart/form-dataのものはreq.bodyとして取り出すことができるようになります。
取り出した{name,cuttime,shampootime,colortime,spatime}をPostgresへ送って、更新に成功したらその成功のメッセージを返し、それをレスポンスとしてWEBページ側へ返す処理をコーディングしました。
Data.jsのコーディング
では、実際の処理をData.jsの中に書いていきましょう。
module.exportsの中に次のコードを追加しましょう。
updateUser: ({id,name,cuttime,shampootime,colortime,spatime}) => {
return new Promise((resolve,reject)=>{
const update_query = {
text:`UPDATE users SET (display_name,cuttime,shampootime,colortime,spatime) = ('${name}',${cuttime},${shampootime},${colortime},${spatime}) WHERE id=${id};`
}
connection.query(update_query)
.then(res=>{
console.log('お客さま情報更新成功');
resolve('お客さま情報更新成功');
})
.catch(e=>console.log(e.stack));
});
}
ごくごく単純なコードですね。引数として渡ってきた{id,name,cuttime,shampootime,colortime,spatime}を使って、UPDATEクエリ文の実行によりデータテーブルを更新してあげてます。
そして更新できたら'お客さま情報更新成功'というメッセージをresolveしていますので、先ほどのcontrollers/api.jsのコードにつながっていきます。
ではこれをデプロイして、確認してみましょう。
お客さま情報更新の確認
お客さま情報管理画面で、カードを立ち上げます。編集ボタンをクリックします。
ID:1の人のデータですね。ではカット時間を25へ変えてみましょう。ただし、入力は下のように全角で25と入力します。
この状態で送信ボタンをクリックします。
このようにちゃんとアラートが出ましたね。ではOKでアラートを消し、今度はちゃんと半角の数値で入力してみます。
Cut→25、Color→50へ変更しました。では送信ボタンをクリックします。
何にも変化ないように思われるかもしれませんが、JavaScriptコンソールを立ち上げると、画像のようにResponseが返ってきていることが分かります。
今の状態だと画面が自動でリロードしませんので、手動で再度/usersを読み込みましょう。
おおお!しっかり更新されていますね!!
念のため、サーバ側のデータも更新されたか確認しましょう。VS CodeのターミナルでPostgresへ接続します。
$ heroku pg:psql
DATABASE=> select * from users;
でusersテーブルのデータを確認してみましょう。
おおお!id=1の人のcuttimeが25へ、colortimeが50へ変更されていることが分かりますね!
プログラムの微修正
では最後に、プログラムの微修正を行いたいと思います。確認のところで見てきて、あれっ?と思ったところがあるかと思います。以下3点を修正していきたいと思います。
■更新に成功したら管理画面上で更新成功などのメッセージを出したい
■更新成功したら自動で画面をリロードしたい
■更新したデータが一番下になるので、idの昇順で並べたい
・更新成功のメッセージ
controllerのapi.jsの中でres.status(200).send(message)として、データ更新のmessageをres.sendしてあげてますので、これを管理画面上で表示させましょう。以下users.js内のコード変更です。
fetch(`/api/users/${userDataArray[0]}`,{
method:'POST',
body:data,
creadentials:'same-origin'
})
.then(response=>{
console.log('response:',response);
if(response.ok){
response.text()
.then(text=>alert(`${text}`))
.catch(e=>console.log(e));
}else{
alert('HTTPレスポンスエラーです')
}
})
.catch(e=>{
throw e;
});
では再度情報更新してみますと。
しっかりアラートメッセージが出ましたね!
・ページの自動リロード
alertの下にdocument.location.reload()を入れてみましょう。
response.text()
.then(text=>{
alert(`${text}`);
document.location.reload();
})
.catch(e=>console.log(e));
では、デプロイしてもう一度ページを読み込み、更新してみます。
自動でページの再読み込みが行われ、更新した値も反映されていることかと思います。
・idの昇順に並び替え
表の並び替えはusers.jsで行います。fetchしたデータをcreateTableの引数に渡しているので、このcreateTableメソッド内で並び替え処理を行いましょう。
// usersData配列へ配列を格納
usersData.push([
usersObj.id,
usersObj.display_name,
resistrationDate,
usersObj.cuttime,
usersObj.shampootime,
usersObj.colortime,
usersObj.spatime,
nextReservationDate
]);
//idの昇順に並び替え
usersData.sort((a,b)=>{
if(a[0] < b[0]) return -1;
if(a[0] > b[0]) return 1;
return 0;
});
二次元配列usersDataを生成したその後に昇順ソート処理を追加しましょう。
これで一番id番号が若いお客さま情報を更新した時も、そのお客さま情報はテーブルの下に行くことなく、idが若い順に並ぶことと思います。
さて、今回は以上で終了です。
APIサーバ構築のGET編、POST編さえ理解してしまえば、あとは自由に管理画面を構築できると思います。
予約管理画面はお客さま情報管理画面より若干複雑ではありますが、同じ原理で構築することが可能です。
ということで長かったLINE自動予約BOTシリーズも次回を最終回としたいと思います。
本記事が少しでも参考になりましたら「スキ」をいただけると幸いです。
最後までお読みいただき、ありがとうございました。
PS:
MENTAにてプログラミング、LINEBOT開発のサポートをさせていただいてます。お気軽にお問い合わせください。