Lesson 5

DBとの連携

Lesson 5 Chapter 1
mysqlモジュールのインストール

Lesson4では、Expressを用いたHTTPサーバーの構築を学びました。リクエストやレスポンスの処理を覚えたことで、本格的なWebアプリケーションへの道筋が見えてきたと思います。

続くLesson5では、Expressを用いたデータベース(DB)サーバーとの接続を学びます。DBとの連携もまた、今日のWebアプリケーションには欠かせないものです。

このLessonを通して、参照・追加・更新・削除といったDBの基本操作を身につけてください。

プロジェクトの準備

実践の前準備として、新規のプロジェクトを作成しましょう。任意の作業ディレクトリに移動し、以下のコマンドを入力します。

ターミナル(Mac / Linux / Windows共通)
$ mkdir express-mysql-app
$ cd express-mysql-app
$ npm init -y
$ npm install express ejs
                    

プロジェクト直下に「package.json」ファイルが作成され、dependenciesの項目にExpressとEJSのバージョンが記されていることを確認してください。

続いて、「app.js」ファイルと「views」ディレクトリを作成します。「views」ディレクトリ配下には「index.ejs」ファイルを作成しましょう。

コマンドライン上では、以下の操作で作成できます。

ターミナル(Mac / Linux)
$ touch app.js
$ mkdir views
$ touch views/index.ejs
                      
ターミナル(Windows)
$ New-item app.js
$ mkdir views
$ New-item views/index.ejs
                      

念のため、以下の画像と同じディレクトリ構成であることを確認してください。

dir_ch1-1.png 現在のディレクトリ構成

以上でプロジェクトの初期化は完了です。ここからプロジェクトを編集していきましょう。

プロジェクトの編集

続いて「app.js」および「index.ejs」のファイルを編集します。

app.js

先にExpressを記述しましょう。先ほど作成した「app.js」を以下のように編集します。現時点では、リクエストに対してページを表示するだけの単純な実装です。

app.js
const express = require('express')
const path = require('path')
const app = express()
const PORT = 3000

// ejs用の設定追加
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

app.get('/', (req, res) => {
  res.render('index.ejs')
})

app.listen(PORT, () => {
  console.log(`Starting server on PORT: ${PORT}`)
})

index.ejs

EJSも記述します。「index.ejs」を以下の通り編集してください。こちらも現時点では確認用のコードです。

index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello EJS!</h1>
  </body>
</html>

以上でプロジェクトの基本編集は完了です。それでは、実際にサーバーを起動してみましょう。

プロジェクトの起動

node app.jsを打ち込んでサーバーを起動します。

サーバーの起動
cd express-mysql-app
node app.js
                      

サーバーが起動されたら、任意のブラウザから「https://localhost:3000」にアクセスしてください。「Hello EJS!」とページに表示されたら成功です。

mysqlモジュールのインストール

MySQLと接続するためには、ドライバーであるmysqlモジュールが必要です。

mysqlモジュールはExpressやEJSと同様に、npmを使ってインストールできます。

~/express-mysql-app
$ npm init mysql

MySQLとmysql

紛らわしいですが、以降「mysql」と書く場合はNode.jsのパッケージ名を指し、「MySQL」と書く場合はデータベース管理システム(DBMS)の名称を指すことにします。両者を混同しないように注意してください。

インストールが終わったら、「package.json」にあるdependenciesプロパティを参照してください。その中に「mysql」の項目があればインストール完了です。

まとめ

今回は「mysqlモジュール」のインストールを行いました。作成したプロジェクトは、次のチャプター「MySQLとの接続」で使用します。

Lesson 5 Chapter 2
MySQLとの接続

今回のChapterでは、ExpressとMySQLの接続方法について解説します。両者を接続することで、Express上から簡単にデータの取得や更新が行えるようになります。

一部SQLの操作を必要とする箇所もありますが、解説通り行えば知識がなくても問題ありません。順を追って学習を進めていきましょう。

DBとの接続にはMySQLのインストールが必要です。インストールしていない方はPROQの「MySQLのレッスン」などを参考に環境を構築してください。
また、前回のChapter1でmysqlパッケージをインストールしていない方も、忘れずに準備を済ませておきましょう。

DBの準備

初めに接続先のDBを作成します。以下に従って、コマンドラインからMySQLにログインしましょう。

ターミナル
$ mysql -u ユーザー名 -p
Enter password:
                    

パスワードを聞かれるため、環境構築時に設定した自身のパスワードを打ち込んでください。

mysql_ch2-1.png MySQLのコンソール画面

画像のようなコンソール画面に移行し、mysql>と表示されたらログイン成功です。

DBの作成

それではDBの作成を進めていきます。念のためSHOWコマンドを使用し、既存のDBがあるかどうか確認してください。

MySQL
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
                    

次にCREATE文を使用し、任意の名前でDBを作成します。ここではexpress_mysql_todoとしておきましょう。

MySQL
mysql> CREATE DATABASE express_mysql_todo;
Query OK, 1 row affected (0.00 sec)
                    

再度SHOWコマンドを入力し、DBの一覧を表示します。作成したDB(express_mysql_todo)が存在すれば成功です。

MySQL
mysql > SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| express_mysql_todo |
| mysql              |
| performance_schema |
+--------------------+
                    

テーブルの作成

続いてテーブルを作成します。USEコマンドを使用し、先ほど作成したexpress_my_todoを選択してください。

MySQL
mysql> USE express_mysql_todo;
Database changed

Database changedと表示されたら成功です。続けてCREATE文でテーブルを作成しましょう(テーブル定義については後述します)。

MySQL
mysql> CREATE TABLE todos (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255));
Query OK, 0 rows affected (0.02 sec)
                    

それではテーブルの一覧を見てみましょう。以下のようにテーブルが作成されていることが確認できたでしょうか。

MySQL
mysql> SHOW TABLES;
+------------------------------+
| Tables_in_express_mysql_todo |
+------------------------------+
| todos                        |
+------------------------------+
1 row in set (0.00 sec)
                    

テーブルの中身も確認します。SHOW COLUMNSコマンドを使って以下のように入力してください。

MySQL
mysql> SHOW COLUMNS FROM todos;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| title | varchar(255) | YES  |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

上記の通りテーブル定義が表示されていれば完了です。

テーブル定義

今回は単純なTodoアプリを想定し、「id」「title」という2つのカラムを用意しました。それぞれの説明は以下の通りです。

カラム データ型 説明
id 整数型 Todo項目のID。識別用に自動で割り振られる。
title 文字列型 Todo項目のタイトル。

データの挿入

INSERT文を用いて、これから扱うテストデータを追加します。

MySQL
mysql> INSERT INTO todos (title) VALUES ('First Todo');
Query OK, 1 row affected (0.00 sec)

念のためSELECTを使ってカラムの値を確認しましょう。


mysql> SELECT * FROM todos;
+----+------------+
| id | title      |
+----+------------+
|  1 | First Todo |
+----+------------+
1 rows in set (0.00 sec)
                    

以上でDBの準備が整いました。この「express_my_todos」に対して、HTTPサーバーの側から接続を試みます。

DBへの接続

今まではMySQLに対して直接クエリを打ち込んでいましたが、今度は同じ操作をExpressから行えるようにします。

やや複雑な記述が増えますが、ひとつひとつ理解した上で進めてください。

接続には、本章のChapter1でインストールしたmysqlモジュールを利用します。まずは必要なディレクトリとファイルを作成しましょう。

ターミナル(Mac / Linux)
$ cd express-mysql-todo
$ mkdir lib && cd lib
$ mkdir database && cd database
$ touch client.js && touch execute.js

画像と同じ構成になっていればOKです。

dir_ch2-1.png 現在のデイレクトリ構成

mysqlクライアントの作成

まずは「mysqlクライアント」を作成します。 mysqlクライアントとは、DBへの接続情報を保持したオブジェクトです。DBへの操作を行うときは、このオブジェクトを参照してクエリを実行します。

「client.js」ファイルを開き、以下の通り記述してください。

プロパティ
host localhost
user MySQLのユーザー名
password MySQLのパスワード
lib/database/client.js
const mysql = require('mysql')

const mysqlClient = mysql.createPool({
  host: 'localhost',
  user: 'admin',
  password: 'password',
})

module.exports = mysqlClient

コードの解説は次の通りです。

  • require('mysql')でmysqlモジュールを読み込んでいます。
  • const clientにDBへの接続情報を保持したオブジェクトが格納されます。
  • module.exportsで別ファイルにて読み込めるようにモジュール化しています。

モジュール化したclientオブジェクトを外部ファイルから参照し、DBへのクエリを実行します。

クエリ実行用モジュールの作成

次に、クエリを実行するためのモジュールを作成します。このようにモジュールを分割することで、保守性の高いコードが記述可能です。

execute.js
const executeQuery = (pool, query, params = undefined) => {
  return new Promise((resolve, reject) => {
    pool.query(query, params, (err, results, fields) => {
      if (err) reject(err)
      resolve({ results, fields })
    })
  })
}

module.exports = executeQuery

引数は以下の3つを受け取ります。

引数 説明
pool MySQLの接続情報を保持したオブジェクト
query DBに対して実行するクエリ
params クエリの引数(WHERE句などに使用)

「Promise」という言葉に戸惑った人も多いでしょう。簡単に説明すると、Promiseは非同期処理を同期的に処理するためのオブジェクトです。

そもそもJavaScriptでは、必ずしも記述した順番に処理が終わるわけではありません。上記の例におけるDBアクセスのように、時間のかかる処理は順不同で(非同期的に)終了する状況が頻繁に発生します。

非同期で複数の処理を進められるというのは、ネットワーク通信を行う上で非常に便利です。しかし、一方で処理が終わるタイミングを制御しづらいという欠点もあります。

Promiseはこの欠点を補い、データの取得やエラー処理を円滑に行うための仕組みです。

Lesson6-3では、Promiseを用いたエラーハンドリングについて学びます。非同期処理の仕組みもそこで詳しく触れることにしましょう。

クエリ実行

それでは、Express上からDBに対してクエリを実行しましょう。app.jsを以下のように編集してください。

app.js
const express = require('express')
const path = require('path')
const executeQuery = require('./lib/database/execute.js')
const mysqlClient = require('./lib/database/client.js')
const app = express()
const PORT = 3000

...省略
app.get('/', async (req, res) => {
  try {
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    const { results } = await executeQuery(mysqlClient, 'SELECT * FROM todos')
    console.log('The title is: ', results[0].title)
  } catch (err) {
    console.log(err)
  }
})
...省略

こちらもすべての挙動を理解する必要はありません。簡単にコードを解説しましょう。

  • executeQueryは前掲のモジュールです。ここでは、第1引数としてmysqlクライアント、第2引数として実行するクエリ文を指定しています。
  • todosテーブルから取得したデータはresultsに格納され、console.logにより出力されます。

ブラウザで「http://localhost:3000/」にアクセスしましょう。リクエストに対してクエリが実行された結果、下記画像のようにDBの取得データが表示されるはずです。

query_ch2-1.png クエリ実行結果「The title is: First Todo」

以上で、今回の目的であったMySQLとの接続は完了しました。

まとめ

今回はMySQLとの接続を行いました。大事な部分を復習しておきましょう。

  • MySQLへの接続はmysqlモジュールを使用する。
  • Expressでmysqlクライアントのオブジェクトを作成し、MySQLへの接続情報を保持する。
  • MySQLへのクエリはmysqlクライアントを介して実行する。

次のChapterでは、MySQLからデータを取得したり更新したりする「CRUD操作」を画面上で行います。徐々に内容が難しくなっていきますが、過去のChapterを振り返りつつ学習を進めていきましょう。

Lesson 5 Chapter 3
DBから取得したデータを表示する

Chapter2では、MySQLに接続してDBのデータを取得しました。今回のChapterでは、取得したデータを画面上に表示させてみましょう。

実装を通して、Webアプリケーションの基本概念である「CRUD」への理解を深めてください。

CRUDとは

「CRUD」は、データベースが備えるべき4つの機能をまとめた用語です。具体的にはCreate(生成)、Read(読み取り)、Update(更新)、Delete(削除)の頭文字を取って命名されました。

名前 操作 対応するSQL文
Create 生成 INSERT
Read 読み取り SELECT
Update 更新 UPDATE
Delete 削除 DELETE

上記の表から分かるように、MySQLなどの一般的なデータベースにはCRUDの機能が備わっています。INSERTSELECTは前回のチャプターでも使用しており、覚えている方も多いでしょう。

CRUDの原則はデータベースに限った話ではありません。DBを用いるWebアプリケーションもまた、上記の4要素を満たしている必要があります。

たとえば、商品の在庫管理システムを想像してください。きっと画面上には登録(Create)や検索(Read)、変更(Update)、削除(Delete)のボタンが並んでいるはずです。仮にどれか一つの機能でも欠けてしまえば、とてもアプリケーションとは呼べない代物になるでしょう。

CRUDの原則は当たり前のように思えるかもしれません。しかし、システムが複雑になるほどミスも生じやすくなります。「商品の登録機能は実装したが、顧客の連絡先情報が更新できない」などといった事態が起きないように、日頃からCRUDを意識した設計を心がけてください。

Todoアプリを作成する

ここからは実践として、簡単なTodoアプリを作ってみましょう。簡単といっても、アプリとして必要な機能を持たせるためには、先に述べたCRUDに対する理解が求められます。

テストデータのインサート

最初にテストデータをDBにインサートします。前回同様、コマンドラインからMySQLのコンソールにログインしてください。

ターミナル
mysql -u 自身のユーザー名 -p
Enter password: 自身のパスワード

MySQLのコンソールに移ったら、前回のチャプターで作成したexpress_mysql_todosのDBを選択します。

ターミナル
USE express_mysql_todo;
Database changed

以下に従って、テストデータを挿入するためのクエリを入力します。

ターミナル
INSERT INTO todos (title) VALUES ('おにぎりを買う'), ('お茶を買う'), ('散歩に行く');
Query OK, 1 row affected (0.00 sec)

挿入用のクエリが実行されました。念のためテストデータが入っているか確認しましょう。

ターミナル
SELECT * FROM todos;
+----+----------------+
| id | title          |
+----+----------------+
|  1 | First Todo     |
|  2 | おにぎりを買う |
|  3 | お茶を買う     |
|  4 | 散歩に行く     |
+----+----------------+
4 rows in set (0.00 sec)

上記のように一覧表示されていれば、テストデータの準備は完了です。

DBから取得したデータを表示する

次に、挿入したテストデータをExpressで取得し、画面に表示してみましょう。Expressの取得処理は以下のように記述できます。

app.js
...省略

app.get('/', async (req, res) => {
  try {
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    const { results } = await executeQuery(mysqlClient, 'SELECT * FROM todos')
    // 変更箇所
    console.log(results)
  } catch (err) {
    console.log(err)
  }
})

...省略

編集が終わったら、コマンドラインにnode app.jsと打ち込んでサーバーを起動します。ブラウザから「http://localhost:3000/」にアクセスしてください。

画像のように複数のTODOが取得できたかと思います。

mysql_ch3-2.png すべてのTODOを取得

viewファイルの編集

Experessを用いてDB上のデータを取得することができました。今度は、取得したデータをWebページに表示させてみましょう。

「index.ejs」を以下の通り編集します。

index.ejs
<body>
  <h1>TODO アプリ</h1>
  <ul>
    
    <li></li>
    
  </ul>
</body>

forEach構文を用いて、配列(todo)のデータをひとつずつ取り出しています。その中のtitleを表示するため、EJSの記法が使われていることを確認してください。

レスポンスの作成

最後に「app.js」を修正します。Expressのres.renderメソッドによって、EJSのテンプレートにデータが渡されていることを確認しましょう。

app.js
app.get('/', async (req, res) => {
  try {
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    const { results } = await executeQuery(mysqlClient, 'SELECT * FROM todos')
    res.render('index.ejs', { todos: results })
  } catch (err) {
    console.log(err)
  }
})

書き換えが完了したらnode app.jsでサーバーを起動し、ブラウザで「http://localhost:3000/」に接続してください。画像のように表示されれば成功です。

browser_ch3-1.png

まとめ

今回は取得したDBのデータを処理し、EJSを用いて画面に反映させました。これでCRUDにおける「Read(読み取り)」の機能が実装されたことになります。

残るChapterでは、追加(Create)と更新(Update)の実装を解説します。コードの記述量が増えていきますが、流し読みはせず、できるだけ理解に努めるようにしてください。

Lesson 5 Chapter 4
画面から入力した内容を登録する

Chapter3では、TodoアプリのデータをDBから取得し、Webページに表示する方法を学びました。今回のChapter4では、新たにデータの追加機能を実装します。CRUDの考え方に従えば、「Create(作成)」にあたる箇所です。

クライアントがデータの追加を要求する場合、一般に「POST」メソッドが使用されます。この機会に、Expressを用いたPOSTメソッドの受け取り方を学びましょう。

GETメソッドとPOSTメソッドの違い

「Expressを用いたリクエストデータの取得なら、Lesson4ですでに学んだはず」と思われるかもしれません。しかし、Lesson4で解説したのはGETメソッドによるパラメーターの取得でした。

ブラウザに「localhost:3000/todos/1」と入力したのを覚えていますか? GETメソッドでは、このように取得したい情報をURLの末尾に付け加えます。GETメソッドはWebページの閲覧など、サーバーのデータを取得したい場合に使われる通信方式です。

一方、入力フォームなどを介してサーバーにデータを送りたい場合には、原則としてPOSTメソッドが使われます。POSTメソッドの情報はURLに含まれず、「リクエストボディ」という形で送信されます。

HTTPメソッド 説明
GET 指定したターゲットをサーバーから取得する
POST 指定したターゲットにデータを送る
PUT サーバーにファイルを書き込む
DELETE サーバーのファイルを削除する

その他のHTTPメソッド

他にもHEAD(ターゲットのヘッダ情報を取得する)やPATCH(データを部分的に更新する)など様々なメソッドがあります。

リクエストの形式が異なる以上、これまでのapp.getメソッドは使用できません。以下の項目で、リクエストボディを取得する方法を見ていきましょう。

【参考】リクエストボディとは

リクエストボディは、HTTPリクエストを構成する領域のひとつです。他の領域として、リクエストヘッダーとリクエストパラメーターがあります。

以下の表を参考に、HTTPリクエストの構成を整理しておきましょう。

構成名 説明
リクエストパラメーター HTTPメソッドやターゲット、HTTPのバージョン情報など。
リクエストヘッダー 通信先のホストや認証、文字コード情報など。
リクエストボディ 上記に含まれない長い文字列やパスワードなど。

HTTP通信では、すべてのデータが文字列として送受信されます。この文字列のうち、先頭行に置かれる情報が「リクエストパラメーター」です。具体的には、メソッド(GETやPOSTなど)とターゲット(URL)、HTTPのバージョン情報が格納されています。

続く複数行は「リクエストヘッダー」と呼ばれる領域です。具体的には、通信先のホストや認証、文字コード情報などが格納されています。

そして空白行を挟んだ最後の領域が「リクエストボディ」です。長い文字列やパスワードなどのURLに含めたくない情報は、このリクエストボディに格納されます。

リクエストボディ

リクエストボディは基本的にPOSTやPUTメソッドで用いられ、GETメソッドでは使用されません。

リクエストボディを受け取る

それでは、Expressによるリクエストボディの取得方法を見ていきましょう。手始めとして、「app.js」を以下のように編集してください。

app.js
...省略
// ejs用の設定追加
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

// express 設定 追加
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
...省略

上記のコードでは、Expressの内部機能であるexpress.jsonexpress.urlencodedという二つの関数をミドルウェアとして定義しています。

  • express.json:リクエストボディ(JSON形式)を解析する
  • express.urlencoded:エンコードされた文字列を解析する

body-parser

従来のExpressでは、リクエストボディの解析に外部モジュール(body-parser)が必要でした。最新版(v4.16.0以降)ではexpress.jsonとexpress.urlencodedが実装され、外部モジュールに依存しない設計が可能となっています。

URLに含まれるリクエストパラメータと異なり、リクエストボディはJSON形式で格納されています。また、入力フォームから送信された文字列は「URLエンコード」という形式に変換されており、そのままではNode.jsで扱えません。

上記のミドルウェアによって、通常のオブジェクトに変換することが可能です。

解析された値を受けとる

以上でリクエストボディを受け取る準備が整いました。「app.js」に以下の記述を追加し、新たなエンドポイントを作成します。

app.js
app.post('/todo', (req, res) => {
  console.log(req.body.title)
})

GETメソッドの取得にはapp.getを使用しましたが、POSTメソッドの取得には、app.postを使用します。

  • Todoのタイトルを追加するルーティングになるため、パスは「/todo」とします。
  • リクエストボディはreq.bodyで受け取ります。今回は「title」というプロパティが与えられているため、req.body.titleとしています。

以上でExpress側の実装は完了しました。

formで情報を渡す

続いて、入力フォームからデータを送信できるようにEJSの記述を変更します。

index.ejs
<body>
  <h1>TODO アプリ</h1>
  <ul>
    
    <li></li>
    
  </ul>
  <div>
    <h2>Todo追加</h2>
    <form action="/todo" method="post">
      <input type="text" name="title" placeholder="todo title" />
      <input type="submit" value="送信" />
    </form>
  </div>
</body>

POSTリクエストの送信には、上記のようにformタグを使用します。name属性の値をプロパティ名として、フォームの入力データがリクエストボディに格納されます。

ブラウザから「htto://localhost:3000/」に接続し、画面を確認しましょう。前回チャプターで取得した一覧の下に、「Todo追加」の見出しと入力フォームが表示されているはずです。

browser_ch4-1.png

includeを用いてEJSファイルを分割する

ここで「index.ejs」ファイルを覗いてみると、Todoの一覧と入力フォーム、2つの要素があることに気が付きます。

今回は単純なサンプルコードであるものの、実際のアプリケーション開発では記述量が何倍にも膨れ上がります。将来的なメンテナンス性を考慮すると、あらかじめファイルを分割しておくべきです。

EJSではincludeを使うことで、別のEJSファイルを呼び出すことができました。以下の手順に従ってファイルを分割しましょう。
  1. 「views」配下に「_share」というディレクトリを作成します。
  2. 「_share」配下に「todoList.ejs」と「todoForm.ejs」を作成します。
~/views/ (Mac / Linux)
cd views
mkdir _share
touch ./_share/todoList.ejs && touch ./_share/todoForm.ejs
~/views/ (Windows)
cd views
mkdir _share
New-item ./_share/todoList.ejs && New-item ./_share/todoForm.ejs

各ファイルの内容は以下の通りです。

todoList.ejs
<ul>
  
  <li></li>
  
</ul>
todoForm.ejs
<div>
  <h2>Todo追加</h2>
  <form action="/todo" method="post">
    <input type="text" name="title" placeholder="todo title" />
    <input type="submit" value="送信" />
  </form>
</div>
index.ejs
<body>
  <h1>TODO アプリ</h1>
  
  
</body>

コンポーネント志向

このようにアプリケーションを小さな部品に分ける手法は「コンポーネント志向」とも呼ばれ、現代の開発環境を特徴づけるものです。コンポーネント単位の設計を行うことで、保守性や拡張性の高いシステムが構築できます、

ファイルの記述が終わったら、実際にリクエストを送信してみましょう。

サーバーを起動してブラウザを開き、フォームに適当な文字列を入力して「送信」を押しましょう。成功すれば、入力した文字列がサーバーからレスポンスとして返されます。

送信したデータをDBに登録する

入力フォームから正しく文字列を送ることができました。それでは、サーバー側で受け取った文字列をDBに登録しましょう。

「app.js」のファイルを開き、app.post('/todo')のコードを変更します。

app.js
app.post('/todo', async (req, res, next) => {
  // ① title取得
  const title = req.body.title
  try {
    // ② DB選択
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    // ③ 取得したtitleをDBに登録
    await executeQuery(mysqlClient, 'INSERT INTO todos (title) VALUES (?)', [
      title,
    ])
    // ④ 一覧を再度取得
    const { results } = await executeQuery(mysqlClient, 'SELECT * FROM todos')
    // ⑤ index.ejsを表示
    res.render('index.ejs', { todos: results })
  } catch (err) {
    console.log(err)
  }
})

①リクエストボディの取得、②DBとの接続、③データの読み込み、⑤データの表示については既出の内容です。

③取得したデータをDBに登録する処理を確認してください。executeQueryは以下の働きをしています。

  • INSERTによるクエリを発行し、DBにデータを挿入しています。
  • (?)は動的な値が入ることを意味します。
  • [title,]は引数として、(?)に渡される値を指定します。

画面から動作確認を行う

フォームに値を入力し、「送信」を押してみましょう。入力した文字列がリストに追加されていれば問題ありません。

たとえば下記画像では、リストに「新規」が追加されています。

borwser_ch4-2.png Todo登録

まとめ

今回はTodoアプリに登録機能を追加しました。長いChapterとなったため、最後にポイントをまとめておきます。

  • 入力フォームの情報はPOSTメソッドによりリクエストボディに格納される
  • Expressでリクエストボディを受け取るにはexpress.jsonexpress.urlencodedのミドルウェアが必要
  • 必要なデータの取得にはapp.postメソッド内でreq.bodyを参照する
  • クエリの動的な値には「?」を使用し、executeQueryの第3引数として渡す

次はLessonの総決算として、登録したデータを「更新」します。最後まで振り落とされないように頑張ってください。

Lesson 5 Chapter 5
画面から入力した内容で更新する

Chapter4では、CRUDの「Create」として、フォームの入力内容をDBに登録しました。今回はCRUDの「Update」として、Todoリストのタイトルを更新します。

すでにあるデータを書き換えたい場合、今までとは異なるHTTPメソッドを使用します。また、Express側でのルーティングとあわせて、EJS側で画面切り替えの実装をしなければなりません。

Lesson5を通してアプリケーションの基本を学んできましたが、今回はその決算的な内容です。最後まで腰を据えて取り組んでください。

PUT・PATCHメソッド

クライアントからサーバーのデータを更新したい場合、原則としてHTTPメソッドの「PUT」もしくは「PATCH」が使用されます。両者は似た性質を持っており、しばしば混乱を招くため注意が必要です。

実践に入る前に、PUTとPATCHの違いを押さえておおきましょう。端的にまとめると、両者の違いは以下の通りです。

PUTメソッド PATCHメソッド
既存のリソースを完全に上書きする 既存のリソースを部分的に上書きする

具体的なコードを例示しながら、以下で詳しく解説します。

PUTメソッド:データを全部更新する

対象となるデータを丸ごと書き換えたい場合は、PUTメソッドを使用します。

たとえば、人物の姓名と年齢が格納された、以下のデータがあるとしましょう。このうち姓を「tanaka」→「yamada」に更新したい場合を考えてみます。

{
  lastName: 'tanaka',
  firstName: 'ken'
  age: 18
}

クライアントからPUTメソッドを用いて更新します。リクエストの内容は以下の通りです。

{
  lastName: 'yamada',
}

リクエストの結果、更新されたデータは下記のようになります。

{
  lastName: 'yamada'
}

名前と年齢が削除されてしまいました。送信された値で元のデータが完全に上書きされたためです。このように、PUTメソッドは対象となるデータ全体を更新する特徴を持ちます。

上記の例において、もし姓だけを更新したければ下記のリクエストを送るべきでしょう。

{
  lastName: 'yamada',
  firstName: 'ken',
  age: 18
}

PATCHメソッド:部分更新

対象となるデータを部分的に更新したい場合は、PATCHメソッドを使用します。

先ほどの例で確認しましょう。同じデータに対して、PATCHメソッドで以下のリクエストを送ります。

{
  lastName: 'yamada',
}

リクエストの結果として、データ配下の通り更新されるはずです。

{
  lastName: 'yamada',
  firstName: 'ken'
  age: 18
}

今度は名前や年齢の値が削除されていません。このように、指定した値だけを部分的に更新するのが「PATCH」メソッドの特徴です。

PUTとPATCH:どちらを使うべきか

解説した通り、PUTとPATCHには更新範囲の違いがあります。どちらが良いということはありません。データを全面的に差し替えたい場合はPUT、差分のみ更新したい場合はPATCHと、用途に応じて使い分けるべきでしょう。

今回のTodoアプリでは、既存のデータであるtitleを更新します。データ全体の差分を取る必要はないため、PUTメソッドを採用することにしましょう。

viewの変更

使用するHTTPメソッドが分かったところで、いよいよ実装に移ります。

今回は更新画面を新たに作る必要があるため、どうしても工程を重ねななければなりません。全体としては、以下の3パートに分割されます。

  • EJSのパーツ分割
  • 更新画面の追加
  • 更新完了画面の追加

以下、順を追って解説していきます。

EJSのパーツ分割

まずはEJSのinclude機能を用いて、共通パーツを作成します。異なる画面間で同じパーツを使い回すことで、コードの見通しが良くなる上に、全体の記述量を大幅に減らせます。

既存の「_share」ディレクトリに、以下のファイルを追加してください。

  • head.ejs
  • pageTitle.ejs
~/express-mysql-app/views/_share (Mac / Linux)
touch head.ejs
touch pageTitle.ejs
~/express-mysql-app/views/_share (Windows)
New-item head.ejs
New-item pageTitle.ejs

作成したファイルを以下の通り編集します。

head.ejs
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>
pageTitle.ejs
<h1></h1>

上記をそれぞれ共通ヘッダー、共通ページタイトルとして使い回します。見ての通り小さなパーツですが、それぞれ明確な機能を持っており、分割する単位として適切です。

メイン画面の追加

分割したパーツをそれぞれの画面から呼び出します。新しく追加する画面のファイルは以下の2つです。

  • 更新画面:edit.ejs
  • 更新完了画面:complete.ejs

いずれも「views」配下に作成してください。

ターミナル (Mac / Linux)
cd views
touch edit.ejs
touch complete.ejs
ターミナル (Windows)
cd views
New-item edit.ejs
New-item complete.ejs

それぞれ以下の通り編集していきましょう。

edit.ejs
<!DOCTYPE html>
<html lang="en">


<body>
  
  <form action="/todo/?_method=PUT" method="post">
    <input type="text" name="title" placeholder="todo title" value="">
    <input type="submit" value="更新">
  </form>
</body>

</html>
complete.ejs
<!DOCTYPE html>
<html lang="en">


<body>
  
  <h2>完了</h2>
  <a href="/todo">戻る</a>
</body>

</html>

以上で更新画面・更新完了画面のテンプレートが完成しました。各ファイルの説明は以下の通りです。

edit.ejs

選択したTodoのタイトルを編集するページです。

  • formタグを配置しています。ここに入力された文字列がPUTメソッドによって送信され、Todoのタイトルを更新します。
  • formタグのmethodがPOSTになっていますが、これは所定の記述です。actionのクエリパラメーターに?_method=PUTが指定されていることに注目してください。後述しますが、formタグでExpressにPUTリクエストを送るときは上記の通り記述します。

complete.ejs

Todoのタイトル更新後に遷移するページです。特筆すべきことはありません。「戻る」を押下するとトップページに戻ります。

編集ページへのリンクを追加

最後に、一覧ページから編集ページへ遷移できるようにリンクを追加します。「todoList.ejs」を開き、以下の通り修正してください。

todoList.ejs
<ul>
  
  <li><a href="todo/"></a></li>
  <li></li>
  
</ul>

以上で'/todo/:id'へのリンクが設定され、Todoごとの編集ページに遷移できるようになりました。

Expressの変更

viewのファイルは編集できました。あわせてExpressのルーティングも修正していきましょう。

主な変更としては下記の通りです。

  • ミドルウェアにmethodOverrideを追加
  • ルーティングパス(GET)を'/' => '/todo'に変更
  • 新規ルーティングパス(GET)として'/todo/:id'を追加
  • 新規ルーティングパス(PUT)として'/todo/:id'を追加

ファイルの内容を確認しましょう。ただコピーするのではなく、必ず変更箇所を確認してください。

app.js
const express = require('express')
const path = require('path')
const methodOverride = require('method-override')

// mysql設定読み込み
const mysqlClient = require('./lib/database/client')
const executeQuery = require('./lib/database/execute')

const app = express()
const PORT = 3000

// ejs用の設定追加
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

// express 設定
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(methodOverride('_method'))

app.get('/todo', async (req, res) => {
  try {
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    const { results } = await executeQuery(mysqlClient, 'SELECT * FROM todos')
    res.render('index.ejs', { todos: results })
  } catch (err) {
    console.log(err)
  }
})

app.post('/todo', async (req, res, next) => {
  const title = req.body.title
  try {
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    await executeQuery(mysqlClient, 'INSERT INTO todos (title) VALUES (?)', [
      title,
    ])
    const { results } = await executeQuery(mysqlClient, 'SELECT * FROM todos')
    res.render('index.ejs', { todos: results })
  } catch (err) {
    console.log(err)
  }
})

app.get('/todo/:id', async (req, res) => {
  try {
    // mysqlへの接続
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    // idから該当のTodo取得
    const { results } = await executeQuery(
      mysqlClient,
      `SELECT * FROM todos WHERE id = ${req.params.id}`
    )
    // edit.ejsへの遷移、取得したTodoを渡す
    res.render('edit.ejs', { todo: results[0] })
  } catch (err) {
    console.log(err)
  }
})

app.put('/todo/:id', async (req, res, next) => {
  // idをリクエストパラメーターから取得
  const id = req.params.id
  // titleをリクエストボディから取得
  const title = req.body.title
  try {
    // mysqlへの接続
    await executeQuery(mysqlClient, 'USE express_mysql_todo')
    // Todoのtitle更新
    await executeQuery(
      mysqlClient,
      `UPDATE todos SET title = ? WHERE id = ${id}`,
      [title, id]
    )
    // complete.ejsへの遷移
    res.render('complete.ejs')
  } catch (err) {
    console.log(err)
  }
})

app.listen(PORT, () => {
  console.log(`Starting server on PORT: ${PORT}`)
})

以下は変更点の解説です。

methodOverrideの追加

ミドルウェアとしてmethodOverrideを定義しています。methodOverrideは送信クエリパラメーターで入力されたHTTPメソッドを識別し、PUTDELETEなどのHTTPメソッドを識別します。こうすることで、クライアントはPOST要求を通じてPUT要求を行うことが可能です。

method-overrideモジュール

ExpressはGET・POST・PUT・DELETEのHTTPリクエストを送信できますが、ブラウザ側が対応しているのはGET・HOSTだけです。method-overrideを用いることで、ブラウザをPUT・DELETEに対応させることができます。

Topページのルーティングパス変更

ルーティングパスをapp.get('/') => app.get('/todo')に変更しています。意図としてはPUTの追加でルーティングパスが増えてパスにバラツキが出るからです。

この変更により、トップページにアクセスするときは「http://localhost:3000/todo」を参照する必要があります。

タイトル更新用のパス追加(GET)

Todoのタイトル更新画面として、新たにパスを作成しました。動的なidというパラメーターを受け取り、Todoの詳細情報を保持したedit.ejsへ遷移させます。より具体的には、以下の処理を行っています。

  • mysqlへの接続
  • idに該当するTodoをDBから取得
  • edit.ejsへ遷移し、取得したTodoを渡す

browser_ch5-1.png edit.ejs

タイトル更新用のパス追加(PUT)

実際にTodoのtitleを更新する箇所です。また、以下の処理を行っています。

  • Todoのidtitleを取得
  • mysqlへの接続
  • DB上のTodoのtitleを更新
  • complete.ejsへの遷移

browser_ch5-2.png complete.ejs

画面上でTodoの更新を確認する

それでは実際に更新を行ってみましょう。例によってnode app.jsでサーバーを起動し、ブラウザから「localhost:3000/todo」にアクセスします。画像のように、現在のTodo一覧画面が表示されるはずです。

browser_ch5-3.png index.ejs

「散歩に行く」をクリックすると更新画面(edit.ejs)へ遷移します。更新フォームへ「散歩に明日行く」と入力します。

browser_ch5-4.png edit.ejs

完了画面から「戻る」を押下します。「散歩に行く」から「散歩に明日行く」へ更新されていることが確認できました。一覧画面は現在のTodoをDBから取得します。

DBの値が変更できていることが確認できます。

browser_ch5-5.png index.ejs

DBを確認する

念のためDBも確認してみましょう。mysql -u root -pでパスワードを入力し、MySQLのコンソール画面に入ります。DBに接続後、SELECT文で確認してください。

terminal_ch5-1.png MySQL

「散歩に明日行く」と変わっていることが確認できました。データの永続化も問題なく行えています。

まとめ

以上で「DBとの連携」のLessonは終了です。HTTPサーバーとデータベースを連携したことで、画面上に動的なデータを表示できるようになりました。

Chapter5のまとめは以下の通りです。

  • データの更新にはPUTPATCH)メソッドを使用する
  • app.putメソッドでPUTのルーティングを指定できる
  • UPDATE文を発行し、対象となるデータを更新する

今回は取り上げませんでしたが、さらに理解を深めたい方はCRUDの「Delete」として、Todoの削除機能を追加してみましょう。データの登録・更新・削除機能を実装すると、一気にWebアプリケーションらしくなります。

どれほど大規模なシステムであっても、個々の部品はCRUDのような基本理念に従っています。次のLesson6では、エラーハンドリングの基本と実践を学びましょう。