現役エンジニアがNode.jsを解説! 〜ExpressとMySQLでCRUDのAPIを実装する〜
この講座はYouTubeで動画形式でも用意しています。合わせてご覧ください。
目的
ExpressとMySQLでCRUDのAPIを実装する。
CRUDとは
https://developer.mozilla.org/ja/docs/Glossary/CRUD
CRUD(Create、Read、Update、Delete)は、格納されたデータの操作する方法の頭文字をとった語です。永続化されたストレージの4つの基本的な機能を覚えるのに役立ちます。
今回実装するAPI
前回のTODOの情報を用います。
GET /todoF
POST /todo
PUT /todo/:todoId
DELETE /todo/:todoId
データはJSONで扱うことを想定しています。また、MySQLのパッケージも前回同様、Node.jsで使える物を使用していきます。
APIの実装(POST)
POSTはCRUDのCreateに該当するものでデータの登録をしていきます。
app.post("/todo", (req, res) => {
console.log(req.body);
const todo = {
status: req.body.status,
task: req.body.task,
};
});
HTTPメソッドはpost
で/todo
にアクセスされた場合実行されます。
第二引数のreq
のreq.body
に受け取ったデータが入り、todoの登録に必要な項目を受け取るようにしていきます。
追加する内容のstatus
、task
の2つをクライアントからPOSTリクエストで送ってもらう想定になります。
connection.query("INSERT INTO todo SET ?", todo, (error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
res.send("ok");
});
データの追加はこのMySQLのコードを使用していきます。"INSERT INTO todo SET ?"
の?
の部分に登録したい値を与え、変数をバインドさせてqueryメソッドの第二引数に登録したい値をセットします。今回の例だと変数todo
が?
に内部的にバインドされて登録するSQLが作成される形になります。第三引数error
のコールバック関数でエラーの場合にはerror、問題ない場合okとログを出すようにしています。
APIの実装(PUT)
続いてはPUTメソッドを用いて変更します。
app.put("/todo/:todoId", (req, res) => {
console.log(req.params);
const todoId = req.params.todoId;
console.log(req.body);
const todo = {
status: req.body.status,
task: req.body.task,
};
どのデータを変更するのかの指定はアクセスするパスに値を持たせる形で行います。:todoId
の部分はパスパラメーターで指定した値が取れることになります。
今回の場合データベースに番号でidが振られるので、指定したidの情報を書き換えていきます。下記では上記をデータベースに反映させる操作をするためにconnection.query
を追加します。
connection.query(
"UPDATE todo SET status = ?, task = ? WHERE id = ?",
[todo.status, todo.task, todoId],
(error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
res.send("ok");
}
);
});
今回はデータの更新なのでUPDATE文を実行します.更新したい項目のstatus
、task
へ与えたい値を?
にして渡しています。
更新対象を指定したいのでWHERE
を使ってidを入れてあげます。?
にマッピング、バインドさせる値はqueryメソッドの第二引数[todo.status, todo.task, todoId]
の配列で指定可能です。
APIの実装(DELETE)
DELETEにおいてもPUTと同様にidを指定して削除する形です。
app.delete("/todo/:todoId", (req, res) => {
console.log(req.params);
const todoId = req.params.todoId;
connection.query(
"DELETE FROM todo WHERE id = ?",
todoId,
(error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
res.send("ok");
}
);
});
idの取得についてはPUTと同様になります。
DELETE文"DELETE FROM todo WHERE id = ?"
で消したいデータのidを指定するような処理を書きます。req.params
から受け取ったtodoId
を第二引数に渡しています。
この処理でparams
のtodoId
に合致するTODOのデータが削除される実装になります。
SQLについて
CRUDのAPIをそれぞれ実装してきました。SQLの詳しい説明は省略しました。プログラミング言語からデータベースを操作するのは基本的にはSQL文を動的に作成して実行する処理の繰り返しになります。
論理削除に対応させる
論理削除とはソフトデリートとも言います。先程のAPIの実装(DELETE)の説明の際、動画内では実際にID2のデータを消したのですが、データを消す操作が間違っていた場合に復元不可能となってしまいます。
それを防ぐために実際にデータを消すのでは無く「データが削除された」というフラグを持たせ、取得の際にフラグのないデータだけを返す処理をします。
先程のAPIの操作を論理削除に対応させてみます。
app.delete("/todo/:todoId", (req, res) => {
console.log(req.params);
const todoId = req.params.todoId;
connection.query(
"UPDATE todo SET deleted_at = ? WHERE id = ?",
[new Date(), todoId],
(error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
res.send("ok");
}
);
});
用意したテーブルのdeleted_at
のカラムに削除された日付を入れて、取得の際にはこのカラムに日付の入っていない物を返すようにします。
deleted_at
の更新なので"DELETE FROM todo WHERE id = ?"
部分をPUTで用いたUPDATE文に変更します。
次にGETの操作でdeleted_at
の日付が入っていないデータだけを返すように修正します。
app.get("/todo", (req, res) => {
connection.query(
"SELECT * FROM todo WHERE deleted_at IS NULL",
(error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
console.log(results);
res.json(results);
}
);
});
SELECT * FROM todo WHERE deleted_at IS NULL
にする事によってdeleted_at
がnull
のデータのみ取得するので削除したデータ以外の取得が可能になりました。
合わせてPUTの操作にAND deleted_at IS NULL
を次のように追加すると削除されたデータを編集できないようになります。
connection.query(
"UPDATE todo SET status = ?, task = ? WHERE id = ? AND deleted_at IS NULL",
[todo.status, todo.task, todoId],
(error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
res.send("ok");
}
);
改善を考える
値のバリデーション
テキストや文字列などのデータベースの型があり、そこに想定しない値をフロントエンドから送ってしまった場合があります。
例えば、int型(数字)の項目(今回だとstatus)に文字列の値を保存できないので、今の実装だと全部受け取ってエラーになってしまいます。
改善策としては送信されるデータをデータベースに渡す前想定する値の型などの条件と合っているかバリデーションを行う事で回避できます。
エラーハンドリング
現状、エラーの処理が個別に書いてあります。返すエラーの内容もres.status(500).send("error")
のみで親切なエラーハンドリングとは言えません。
改善案としてはExpressの機能を用いてエラー処理をまとめる、何のエラーなのか条件分岐させてステータスコードやエラーレスポンスのメッセージなどを変更するなどが挙げられます。
フロントエンドはそのエラーの内容を受け取ってエラーに対応する画面の表示を出す事ができるとより良いアプリケーションになります。
よりSQLを扱いやすく
今回はSQL文をMySQLパッケージを使って組み立てました。
TODOテーブルが1個なので問題はないのですが、テーブルが増えた場合にSQLの組み立ては大変になりデバッグなどしづらくなる問題が発生します。
1つの改善策として オブジェクトリレーショナルマッパーの使用 があります。
これを使うとデータベースの操作をJavaScriptであればよりJavaScriptらしく行う事ができます。
まとめ
今回はExpressとMySQLでCRUDのAPIを実装するところを行いました。
次回は今回実装したAPIをフロントエンドから使ってみます。