Chapter 13

ToDoタスクをデータベースに保存しよう

前章でToDoアプリケーション用のデータベースやテーブルを用意しました。
本章では、3章で作成したExpress.jsを利用したToDoアプリケーションに手を加え、ToDoタスクをデータベースに保存します。
これにより、サーバを再起動してもToDoタスクが消えないアプリケーションに改善できます。

mysqlモジュールを用いてデータベースに接続できるようにしよう

プログラム上からデータベースを操作するためにmysqlモジュールを利用します。

まずはmysqlモジュールをインストールしましょう。
node_studyフォルダ内にあるtodoappフォルダにコンソールで移動してください。

以下コマンドでmysqlモジュールをインストールします。
npm install mysql

それでは、Visual Studio Codeでtodoappフォルダを開き、routesフォルダ内にあるindex.jsを開いてください。

まず、require関数でmysqlモジュールを読み込みます。
const router = express.Router();の下に、以下を記載してください。

const mysql = require('mysql');

次にデータベースの接続情報を定義します。
今追記した行の下に1行空け、以下を記載してください。

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '[事前準備で設定したrootユーザのパスワード]',
  database: 'todo_app'
});

passwordは事前準備で設定したrootユーザのパスワードに置き換えてください。
パスワードがhogeの場合、password: 'hoge',となります。

それでは、接続を確認するためのコードを埋め込みます。

POSTリクエスト時の処理内にある、const todo = req.body.add;の上に以下を記載してください。

connection.connect((err) => {
    if (err) {
      console.log('error connecting: ' + err.stack);
      return
    }
    console.log('success');
  });

これにより、接続エラーの場合はコンソール画面にerror connecting:とerr.stackによって、エラー内容が表示されます。
接続に問題がなければsuccessが表示されます。

それでは、node_studyフォルダ内にあるtodoappフォルダにコンソールで移動してください。

以下コマンドを実行し、サーバを起動させてください。
npm start

chromeを開き、http://localhost:3000/にアクセスします。
ToDoタスクの入力フォームが表示されたら、適当なToDOタスクを追加してみてください。

これでPOSTリクエストが送信されたため、接続確認のコードが実行されているはずです。
コンソール画面に戻り、ログを確認してみましょう。

以下のようにsuccessが表示されていれば問題ありません。

GET / 304 17.940 ms - -
GET /stylesheets/style.css 304 1.738 ms - -
POST / 302 36.119 ms - 46
GET / 200 2.351 ms - 411
success
GET /stylesheets/style.css 304 0.495 ms - -

もし、以下のようにerrorが表示された方はパスワード等の接続情報に誤りがないかを確認してください。

GET / 200 14.567 ms - 374
GET /stylesheets/style.css 304 1.980 ms - -
POST / 302 26.757 ms - 46
GET / 200 4.370 ms - 409
error connecting: Error: ER_ACCESS_DENIED_ERROR: Access denied for user 'root'@'localhost' (using password: YES)

ToDoタスクをデータベースに保存する処理を実装しよう

問題なくデータベースに接続できることが確認できたので、ToDoタスクを配列ではなくデータベースに保存するよう変更しましょう。

まず不要なコードを取り除きます。
todos.push(todo);を削除してください。

現時点のindex.jsはこちらです。

const express = require('express');
const router = express.Router();
const mysql = require('mysql');

let todos = [];

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '[事前準備で設定したrootユーザのパスワード]',
  database: 'todo_app'
});

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'ToDo App',
    todos: todos,
  });
});

router.post('/', function (req, res, next) {
  connection.connect((err) => {
    if (err) {
      console.log('error connecting: ' + err.stack);
      return
    }
    console.log('success');
  });
  const todo = req.body.add;
  res.redirect('/');
});

module.exports = router;

上記コードをコピー&ペーストする際は、password: '[事前準備で設定したrootユーザのパスワード]', となっている点に注意してください。

それでは、query()メソッドを利用して、ToDoタスクをデータベースに保存する処理を実装します。
query()メソッドは以下のように記述します。

connection.query(
  [実行するSQL],
  (error, results) => {
   SQL実行後に行う処理を書く
  }
);

const todo = req.body.add;の上に上記のコードを記述してください。

[実行するSQL]部分に実行するSQLを書きましょう。

以下のようになります。

connection.query(
  `insert into tasks (user_id, content) values (1, '${todo}');`,
  (error, results) => {
   SQL実行後に行う処理を書く
  }
);

user_idに入る値は本来であれば、このToDoを追加したユーザのID (usersテーブルのidカラムの値) となりますが、ユーザに関する機能はまだ作成していないため、決め打ちで1としています。
また、ToDoタスクが格納された定数todoは文字列中で展開できるように書く必要があります。

次に、SQL実行後に行う処理を書きましょう。

以下のようになります。

connection.query(
  `insert into tasks (user_id, content) values (1, '${todo}');`,
  (error, results) => {
    console.log(error);
    res.redirect('/');
  }
);

エラーが発生した場合はconsole.log(error);でコンソールに出力されるようにしています。
また、res.redirect('/');をここに移動させています。

現時点のindex.jsはこちらです。

const express = require('express');
const router = express.Router();
const mysql = require('mysql');

let todos = [];

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '[事前準備で設定したrootユーザのパスワード]',
  database: 'todo_app'
});

let todos = [];

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'ToDo App',
    todos: todos,
  });
});

router.post('/', function (req, res, next) {
  connection.connect((err) => {
    if (err) {
      console.log('error connecting: ' + err.stack);
      return
    }
    console.log('success');
  });
  const todo = req.body.add;
  connection.query(
    `insert into tasks (user_id, content) values (1, '${todo}');`,
    (error, results) => {
      console.log(error);
      res.redirect('/');
    }
  );
});

module.exports = router;

それでは、npm startでサーバを起動し、chromeでhttp://localhost:3000/にアクセスしてください。

ToDoタスクの入力フォームが表示されたら、適当なToDOタスクを追加してみてください。

配列に追加する処理を削除したことにより、配列は空のためToDOタスクは表示されません。

それでは、データベースに保存できているかを確認してみましょう。

mysql -u root -p でデータベースにログインしてください。

use todo_app;で使用するデータベース (todo_app) を選択します。

select * from tasks;でtaskテーブルの中身を確認してみましょう。

以下のように、追加したToDOタスクが保存されていれば問題ありません。

+----+---------+----------------------------------------+
| id | user_id | content                                |
+----+---------+----------------------------------------+
|  1 |       1 | 20時までに宿題を終わらせる                 |
+----+---------+----------------------------------------+

idカラムには自動的に採番された値が、user_idカラムには決め打ちとした1が、contentカラムにはフォームから追加したToDoタスクの本文が入っており、期待通り保存できたことが確認できました。

ToDoタスクをデータベースから取得して表示させる処理を実装しよう。

ToDoタスクをデータベースに保存できるようになりましたが、ToDoタスクを表示させる処理は従来のままです。
配列ではなく、データベースからToDoタスクを取得して表示させるように処理を変更しましょう。

まず、index.jsから不要なコードを取り除きます。

let todos = [];を削除してください。

index.jsは次のようになります

const express = require('express');
const router = express.Router();
const mysql = require('mysql');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '[事前準備で設定したrootユーザのパスワード]',
  database: 'todo_app'
});

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'ToDo App',
    todos: todos,
  });
});

router.post('/', function (req, res, next) {
  connection.connect((err) => {
    if (err) {
      console.log('error connecting: ' + err.stack);
      return
    }
    console.log('success');
  });
  const todo = req.body.add;
  connection.query(
    `insert into tasks (user_id, content) values (1, '${todo}');`,
    (error, results) => {
      console.log(error);
      res.redirect('/');
    }
  );
});

module.exports = router;

それでは、先ほどと同様にquery()メソッドを利用して、tasksテーブルに保存されている全てのToDoタスクを取得する処理を実装しましょう。

router.get内の処理は次のようになります。

router.get('/', function (req, res, next) {
  connection.query(
    `select * from tasks;`,
    (error, results) => {
      console.log(error);
      console.log(results);
      res.render('index', {
        title: 'ToDo App',
        todos: results,
      });
    }
  );
});

select * from tasks; でtaskテーブルのレコード全件を取得し、取得したデータ (results) をtodos: results,で送っています。
また、取得したデータの中身を確認するためにconsole.log(results);を埋め込んでいます。

現時点のindex.jsはこちらです。

const express = require('express');
const router = express.Router();
const mysql = require('mysql');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '[事前準備で設定したrootユーザのパスワード]',
  database: 'todo_app'
});

router.get('/', function (req, res, next) {
  connection.query(
    `select * from tasks;`,
    (error, results) => {
      console.log(error);
      console.log(results);
      res.render('index', {
        title: 'ToDo App',
        todos: results,
      });
    }
  );
});

router.post('/', function (req, res, next) {
  connection.connect((err) => {
    if (err) {
      console.log('error connecting: ' + err.stack);
      return
    }
    console.log('success');
  });
  const todo = req.body.add;
  connection.query(
    `insert into tasks (user_id, content) values (1, '${todo}');`,
    (error, results) => {
      console.log(error);
      res.redirect('/');
    }
  );
});

module.exports = router;

それでは、npm startでサーバを起動し、chromeでhttp://localhost:3000/にアクセスしてください。

先ほど追加したToDoタスクが[object Object]と表示されてしまいます。

では、もう1つ適当なToDoタスクを追加してください。

今追加したものも[object Object]と表示されてしまいます。
しかし、2つ分表示されていることで、データ2つ分取得できており、index.ejsで1つずつ表示できていることが分かりました。

それでは、コンソール画面でconsole.log(results);の結果を確認してみましょう。

以下のように、データベースから取得したデータが出力されています。

[
  RowDataPacket { id: 1, user_id: 1, content: '20時までに宿題を終わらせる' },
  RowDataPacket { id: 2, user_id: 1, content: '部屋を掃除する' }
]

以下は、index.ejsのリストからToDoタスクを取り出して表示している部分です。

<% for(let todo of todos){ %>
  <li><span><%= todo %></span></li>
<% } %>

以前はToDoタスクの本文のみを配列に保存していたため、以下のようになっていました。
そのため、for文で配列から取り出したデータをそのまま表示させていました。

['20時までに宿題を終わらせる','部屋を掃除する']

しかし、データベースから取得したデータは、配列の中にオブジェクトで各カラムのデータを持っているため、オブジェクトのまま表示しようとして[object Object]となってしまいました。

表示させたいデータはオブジェクト内のcontentの値のため、index.ejsのfor文を以下に変更します。

<% for(let todo of todos){ %>
  <li><span><%= todo.content %></span></li>
<% } %>

<%= todo.content %>とすることで、オブジェクトtodoのcontent(ToDoタスクの本文)を表示するように変更しました。

それでは、choromeに戻り、http://localhost:3000/のページをリロードしてください。
今回はサーバ側には変更がないため、ページリロードのみで反映されます。

ToDoタスクの本文が表示されるようになりました。

これで、3章で作成したToDoアプリケーションをデータベースと連携させ、データを永続化させることができました。
次章ではクエリビルダというSQLを簡単に組み立てることができる機能を利用します。