現役エンジニアがNode.jsを解説! 〜ExpressとMySQLでCRUDのAPIを実装する〜

公開日:2022-09-29
JavaScript
Node.js

https://www.youtube.com/watch?v=ssNbGVeEOOY

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

目的

ExpressとMySQLでCRUDのAPIを実装する。

CRUDとは

https://developer.mozilla.org/ja/docs/Glossary/CRUDhttps://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にアクセスされた場合実行されます。
第二引数のreqreq.bodyに受け取ったデータが入り、todoの登録に必要な項目を受け取るようにしていきます。
追加する内容のstatustaskの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文を実行します.更新したい項目のstatustaskへ与えたい値を?にして渡しています。
更新対象を指定したいので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を第二引数に渡しています。
この処理でparamstodoIdに合致する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_atnullのデータのみ取得するので削除したデータ以外の取得が可能になりました。
合わせて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をフロントエンドから使ってみます。