選考直結型ガクセイハッカソン 2025.03の開催レポート
2022-10-02 公開
この講座はYouTubeで動画形式でも用意しています。合わせてご覧ください。
フロントエンドから実装したAPIを使おう。
app.post("/todo", (req, res) => {
console.log(req.body);
const todo = {
status: req.body.status,
task: req.body.task,
};
});
connection.query(
"INSERT INTO todo SET ?",
todo,
(error, results) => {
if (error) {
console.log(error);
res.status(500).send("error");
return;
}
res.send("ok");
});
INSERT INTO todo SET ?の部分で?に渡す値がオブジェクトの場合、不正なデータを含むリクエストを受信することでSQLインジェクションができてしまうと分かったので訂正します。今回だとtodoという変数にstatusとtaskというプロパティをもつオブジェクトを渡してしまっています。
この問題についてコチラの記事も参考にしてみてください。
const connection = mysql.createConnection({
host: "127.0.0.1",
user: "root",
database: "db",
// 本番環境では環境変数を使うようにする
password: "example",
// SQLインジェクション対策
stringifyObjects: true,
});
SQLインジェクション対策のためにcreateConnectionの中の
stringifyObjectsの設定がデフォルトだとfalseになっているのをtrueに設定します。
この場合、コードの書き方を変更する必要があります。
"INSERT INTO todo SET ?",
todo,
上記の部分を次のように変更します。
"INSERT INTO todo (status, task) VALUES (?, ?)",
[todo.status, todo.task],
先程まではオブジェクトを?に渡していたのですが、
それを配列でオブジェクトのプロパティにそれぞれ渡しています。
const ulRef = document.querySelector("ul");
const inputRef = document.querySelector("#todo-task");
const selectRef = document.querySelector("#todo-status");
const editItemIdRef = document.querySelector("#edit-item-id");
const submitButtonRef = document.querySelector("#submit-button");
const cancelButtonRef = document.querySelector("#cancel-button");
<div>
<label id="item-label" for="item">Enter a item:</label>
<input id="edit-item-id" type="hidden" >
<input type="text" name="item" id="todo-task">
<select name="todo-status" id="todo-status">
<option value=""></option>
<option value="1">INCOMPLETE</option>
<option value="2">PROGRESS</option>
<option value="3">PENDING</option>
<option value="4">COMPLETE</option>
</select>
<button id="submit-button">Add item</button>
<button id="cancel-button">Cancel</button>
</div>
getStatusNameという関数では、
APIでstatusを取得した際にstatus情報は数字で返ってくるので
それらをわかりやすいように対応する文字列に変換する処理を行なっています。
const getStatusName = (status) =>
status === 1
? "INCOMPLETE"
: status === 2
? "PROGRESS"
: status === 3
? "PENDING "
: "COMPLETE";
handleResetFormはinputやselectで選択されている値を初期化する関数で、
フォームのcancelボタンを押した際に行われる処理です。
他にも使いまわしているところがあるので関数で切り出しているような形になります。
const handleResetForm = () => {
inputRef.value = "";
selectRef.value = "";
editItemIdRef.value = "";
submitButtonRef.textContent = "Add Item";
};
handleSelectEditItemは編集するアイテムを選択した際に実行される関数となっています。
フロント側のeditボタンを押した際に情報が反映される処理になります。
const handleSelectEditItem = ({ id, task, status }) => {
editItemIdRef.value = id;
inputRef.value = task;
selectRef.value = status;
submitButtonRef.textContent = "Edit Item";
};
この辺から実際にAPIが絡んだ操作になります。
API通信は非同期処理になるのでasync/awaitで書いています。
fetch(`${url}/todo`)でGET/todoを実行して、todoアイテム一覧を取得しています。
また、res.json()ではfetchでデータを取得した際に、そのデータがJSONフォーマットで返ってくる場合は
jsonメソッドを実行してあげるとオブジェクトに変換される処理となります。
const handleGetItems = async () => {
const res = await fetch(`${url}/todo`);
const todos = await res.json();
ulRef.removeChild(ulRef.firstChild)でリストの中にある項目を1回全部消す操作をしています。handleGetItemsでデータを取得。それをtodoリスト画面に反映させる処理となります。
そのため、アイテムを追加、編集して、もう一度データを取得し画面反映をする際に、前のリストが残っていると追加でリストが表示されてしまうので一旦全部消してからリストを追加する操作をしています。
while (ulRef.firstChild) {
ulRef.removeChild(ulRef.firstChild);
}
todos.forEach((todo)でtodoのアイテムを1個ずつ表示する処理を行なっています。
inputRef.value = "";
todos.forEach((todo) => {
const spanRef = document.createElement("span");
const liRef = document.createElement("li");
spanRef.textContent = `${todo.task} - ${getStatusName(todo.status)}`;
liRef.appendChild(spanRef);
今回のコードのように共通処理はできるだけ括り出すようにすると、
デバッグや使い回しがしやすくなるのでおすすめです。
前回までの内容からフロントエンドの実装をすることによって、
Node.jsでアプリケーションを作るイメージがしやすくなったはずです。