ガクセイハッカソン

現役エンジニアがNode.jsを解説! 〜フロントエンドから実装したAPIを使おう〜

2022-10-02 公開

この講座はYouTubeで動画形式でも用意しています。合わせてご覧ください。

目標

フロントエンドから実装したAPIを使おう。

前回の説明の補足(SQLインジェクション対策)

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という変数にstatustaskというプロパティをもつオブジェクトを渡してしまっています。
この問題についてコチラの記事も参考にしてみてください。

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";
};

TODOリストの取得と画面への反映(handleGetItems)

この辺から実際に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();

TODOリストの取得と画面への反映 ( while (ulRef.firstChild) )

ulRef.removeChild(ulRef.firstChild)でリストの中にある項目を1回全部消す操作をしています。handleGetItemsでデータを取得。それをtodoリスト画面に反映させる処理となります。
そのため、アイテムを追加、編集して、もう一度データを取得し画面反映をする際に、前のリストが残っていると追加でリストが表示されてしまうので一旦全部消してからリストを追加する操作をしています。

  while (ulRef.firstChild) {
    ulRef.removeChild(ulRef.firstChild);
  }

TODOリストの取得と画面への反映 ( todo.forEach )

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でアプリケーションを作るイメージがしやすくなったはずです。

関連記事