SE１年目のJavaScript Webアプリケーションフレームワーク道...Expressで簡易メモ帳アプリ(2)

# JavaScriptのWebアプリケーションフレームワーク道
SEとしての1年目も終わりに差し掛かり、個人的にやってきたJavaScriptのWebアプリ作りで学んできたことを整理して、アウトプットするために記事にしました。

今回はExpressからDB(MySQL)への接続についてです。
前回に引き続き、メモ帳アプリを作っていきます。

# 目標
JavaScriptのWebアプリケーションフレームワークの中で最もシンプルなのが"Express"。
Expressでメモ帳アプリを作成することを目標にします。
記事が長くなりそうだったので、基本編とDB接続編の2本を立てです。
この記事は、**DB接続編**として前回までに作った部分にCRUD処理を追加していきます。

前回の基本編→[SE１年目のJavaScript Webアプリケーションフレームワーク道...Expressで簡易メモ帳アプリ(1)](http://qiita.com/NobiNobiKen/items/900c0403adbad7b843e6)

基本編とDB接続編をまとめた完成版コードはGitHubに置いてありますので、ご自由に御覧ください♪ → [sasaken555/expMemo](https://github.com/sasaken555/expMemo)

##やること
+  Webアプリケーションフレームワークの利用
+  **DB(今回はMySQL)との通信を含むCRUD処理**

##やらないこと
+ ~~ES6の記法~~ → SQLを書く中でバッククオートだけ使ってます。
+ **ORM (O/R マッパー) の利用**
+ **NoSQLDB(MongoDBとか)の利用**


## 実装する要件
基本的なCRUD処理ができれば良いなと。具体的には以下のような感じです。

1.  メモの一覧表示
    1.  最初に表示
    2.  各メモは作成・更新時間も分かるようにする
    3.  各メモは詳細も見れるものとする
2.  メモの登録
    1.  タイトルと本文のみ入力する
3.  メモの更新
    1.  これもタイトルと本文のみ入力する
4.  メモの削除
    1.  消す前の確認画面を入れる

#実行環境  
動作確認はWindows10、GoogleChrome から実施しています。
以下、メインで使う環境たちです。

|名称                |バージョン|内容                   |
|:-------------------|:---------|:----------------------|
|Node.js             |7.4.0     |JavaScriptのランタイム |
|NPM                 |3.10.9    |パッケージを管理する   |
|Express             |4.14.0    |Webアプリケーションフレームワーク |
|MySQL               |5.7       |リレーショナルデータベース |

*前提*
Node.js と MySQLはローカルにインストールしています。

#実装
## NodejsとMySQLの連携
こちらの記事を参考にさせていただきました。
→[Node.jsでMySQLを使うメモ](http://qiita.com/PianoScoreJP/items/7ed172cd0e7846641e13)

NodejsとMySQLを連携させるには、node-mysqlを使います。
参考記事と、[node-mysqlの公式](https://github.com/mysqljs/mysql)のREADMEを読みつつ進めます。

### Express側の準備
まずはnode-mysqlを手に入れるところから。
公式の通り、ターミナルから以下１行を叩けばOK！

```
npm install --save mysql
```

これで後はNodejs側から`require`で読み込めばMySQLと接続する処理を行えます。
DBからメモ全件のデータを主として、メモ一覧に表示する処理を書いてみます。

```js:routes/index.js
var express = require('express');
var router = express.Router();

// DB接続定義
var mysql = require('mysql');
var connection = mysql.createConnection({
  host: 'localhost',
  user: 'EditUser',
  password: 'EditYourPass',
  database: 'memopad',
  port: 3306,
  dateStrings: true,
  timezone: 'jst'
});


/* 一覧画面表示処理 */
router.get('/', function(req, res, next) {
  // メモ一覧をDBから取り出してmemoItemsに格納
  var  memoItems = [];

  // DB接続処理
  connection.connect();
  var sql = 'SELECT * FROM memo ORDER BY id ASC;'
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    memoItems = results;
    res.render('index', { pageTitle: 'メモ一覧', memoItems: memoItems });
  });
  connection.end();
});
```

`createConnection`の部分でMySQLとの接続の準備を行い、
`connect()` で接続、`end()` で切断します。
これでOK！


### DB側の準備
連携先のデータベースとテーブルを以下のような形で準備します。

データベース名：memopad
テーブル名：memo

|  項目名 |   データ型  | Null | Key | その他 |
|:-------:|:----------:|:-:|:-:|:-:|
|id       |int         |NO   |PRI|AUTO_INCREMENT |
|title    |varchar(50) |NO   |   |   |
|memo     |varchar(280)|YES  |   |   |
|created  |datetime    |NO   |   |   |
|modified |datetime    |YES  |   |   |

Node.js側ではメモのidを基準に処理を行うので、NOT NULL, PRIMARY KEYに設定しておきます。
DBの準備ができたら、後は5件ほど適当なデータを入れておきます。


### 一覧の稼働確認
一旦ここまでで動くか確認します。

<img width="1034" alt="一覧画面_v2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/137947/27e60004-5530-edcd-4a40-46bac95f7cfe.png">


上手くいけました！
ただし、ここで１つ罠がありました。

#### Connectionの罠
ここで何度かリロードしたり、一度閉じてもう一度アクセスすると。
接続拒否に。。。

コンソール上には以下のようなエラーが吐かれていました。

```
Error: Cannot enqueue Handshake after already enqueuing a Handshake.
```

要は『Connectionが作られているから、２度Connectionを作ってるんじゃねえ』ってことみたいですね。じゃあ『end()を書かなければ１つのConnectionなんでは？』と思い、`connection.end();` を削除してみると...

```
Error: Cannot enqueue Query after invoking quit.
```

次は『接続切ったのに、接続試みるのやめてもらえます？』と怒られてしまいました。どっちをしてほしいんでしょうか...
接続切らずに、２度のconnectionを作成しないようにするには、以下の記事に同様の現象の対処が載っていました。→[Node＋Express＋MySQL備忘録](http://www.natswell.com/techcolumn/2013/04/16/node%EF%BC%8Bexpress%EF%BC%8Bmysql%E5%82%99%E5%BF%98%E9%8C%B2/)

結論から言えば、`connection.connect()` `connection.end()`をどちらも書かないが正解みたいです。もう一度公式を見たら、connect()しなくてもquery()で接続を確立する旨が書いてありましたね。

> [Establishing connections](https://github.com/mysqljs/mysql#establishing-connections)
> However, a connection can also be implicitly established by invoking a query:

なので書き直すとしたらこうなるでしょうか。

```js:routes/index.js
/* 一覧画面表示処理 */
router.get('/', function(req, res, next) {
  // メモ一覧をDBから取り出してsampleMemosに格納
  var  memoItems = [];

  // DB接続処理
  // connection(), end()はそれぞれエラーが出るため記述しない
  // query()で接続している
  var sql = 'SELECT * FROM memo ORDER BY id ASC;'
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    memoItems = results;
    res.render('index', { pageTitle: 'メモ一覧', memoItems: memoItems });
  });
});
```

これでエラーが出なくなりました(\*'ω'*)


### CRUD書き直し
一覧がうまく表示されたところで、他の処理も書いてしまいます。
基本は一覧処理と同じですが、気を付けるべきは時間の処理でした。

```js:routes/index.js
/* 詳細画面表示処理 */
// 正規表現(\\d)で /new とルーティングを区別している
router.get('/:memoId(\\d+)', function(req, res, next) {
  var memoId = req.params.memoId;
  var targetMemo = [];

  var sql = `SELECT * FROM memo WHERE id = ${memoId};`
  console.log(sql);
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    targetMemo = results[0];
    res.render('memoDetail', { pageTitle: 'メモ詳細', targetMemo: targetMemo });
  });
});
```

```js:routes/delete.js
var express = require('express');
var router = express.Router();

// DB接続定義
var mysql = require('mysql');
var connection = mysql.createConnection({
  host: 'localhost',
  user: 'EditUser',
  password: 'EditYourPass',
  database: 'memopad',
  port: 3306,
  dateStrings: true,
  timezone: 'jst'
});


/* 削除確認画面表示処理 */
router.get('/:memoId', function(req, res, next) {
  var memoId = req.params.memoId;
  var targetMemo = [];

  var sql = `SELECT * FROM memo WHERE id = ${memoId};`
  console.log(sql);
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    targetMemo = results[0];
    res.render('deleteMemo', { pageTitle: 'メモ削除', targetMemo: targetMemo });
  });
});


/* 削除処理 */
router.post('/:memoId', function(req, res, next) {
  var memoId = req.params.memoId;

  // SQLの作成
  var sql = `DELETE FROM memo WHERE id = ${memoId}`;

  // 削除実行
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    res.redirect('/');
  });
});

module.exports = router;

```

詳細表示と削除処理は、表示する部分がほぼ共通しているので書くのは困りませんね。
SQLを作る際にES6のバッククオート記法を使っています。`DELETE FROM memo WHERE id = ${memoId}` のところです。こうやって`${}`で変数を直接埋め込めるので便利ですね。
ES6については、かなりNode.jsが対応してきているみたいなので、別の機会にフルES6のNode.jsアプリなんか書いてみたいですね。→[Node.jsのES6対応状況](http://node.green/)

続いて新規作成と更新処理を書きます。
時間の処理だけ気を付けていきます。

```js:routes/new.js
var express = require('express');
var router = express.Router();

// DB接続定義
var mysql = require('mysql');
var connection = mysql.createConnection({
  host: 'localhost',
  user: 'EditUser',
  password: 'EditYourPass',
  database: 'memopad',
  port: 3306,
  dateStrings: true,
  timezone: 'jst'
});


/*
  日本時間返却処理
  mysqlJpTime(date)
  date: Date型のUTC時間の値
*/
function mysqlJpTime() {
  // UTC時間
  var date = new Date();
  // UTC時間を日本時間に修正
  date.setTime(date.getTime() + 32400000);
  // JSのDate型をMySQLのDateTime型に変換
  var mysqlJpTime = date.toISOString().slice(0, 19).replace('T', ' ');
  return mysqlJpTime;
}


/* 新規登録画面表示処理 */
router.get('/', function(req, res, next) {
  res.render('newMemo', { pageTitle: '新規メモ作成' });
});


/* 新規登録処理 */
router.post('/', function(req, res, next) {
  var title = req.body.title;
  var memo = req.body.memo;
  var createdTime = mysqlJpTime();

  var post = {title: title, memo: memo, created: createdTime};
  var sql = `INSERT INTO memo SET ?`
  connection.query(sql, post, function(error, results, fields) {
    if (error) throw error;
    res.redirect('/');
  });
});

module.exports = router;
```

```js:routes/edit.js
var express = require('express');
var router = express.Router();

// DB接続定義
var mysql = require('mysql');
var connection = mysql.createConnection({
  host: 'localhost',
  user: 'EditUser',
  password: 'EditYourPass',
  database: 'memopad',
  port: 3306,
  dateStrings: true,
  timezone: 'jst'
});


/*
  日本時間返却処理
  mysqlJpTime(date)
  date: Date型のUTC時間の値
*/
function mysqlJpTime() {
  // UTC時間
  var date = new Date();
  // UTC時間を日本時間に修正
  date.setTime(date.getTime() + 32400000);
  // JSのDate型をMySQLのDateTime型に変換
  var mysqlJpTime = date.toISOString().slice(0, 19).replace('T', ' ');
  return mysqlJpTime;
}


/* 更新画面表示処理 */
router.get('/:memoId', function(req, res, next) {
  var memoId = req.params.memoId;
  var targetMemo = [];

  var sql = `SELECT * FROM memo WHERE id = ${memoId};`
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    targetMemo = results[0];
    res.render('editMemo', { pageTitle: 'メモ更新', targetMemo: targetMemo });
  });
});


/* 更新処理 */
router.post('/:memoId', function(req, res, next) {
  // 更新する値
  var title = req.body.title;
  var memo = req.body.memo;
  var modifiedTime = mysqlJpTime();
  var memoId = req.params.memoId;

  // SQLの作成
  var sql = 'UPDATE memo SET title = ?, memo = ?, modified = ? WHERE id = ?';
  var post = [title, memo, modifiedTime, memoId];
  sql = mysql.format(sql, post);

  // 更新実行
  connection.query(sql, function(error, results, fields) {
    if (error) throw error;
    res.redirect('/');
  });
});

module.exports = router;
```

ここで引っかかったのは、JavaScriptの時間の処理。以下２つの問題への対処が必要です。ややこしい処理はひとまとめにして目的の時間を返す`mysqlJpTime(date)` を定義しています。

+ new DateはUTC時間  
`new Date` で現在時刻を取得すると、UTC時間になっているようですね。
日本時間に直すためには、９時間分遅らせなきゃいけません。
右の記事を参考にさせていただいて、以下のように書きました。
→[node.jsで現在の時刻を自動で取得する](http://hacknote.jp/archives/11799/)

```
date.setTime(date.getTime() + 32400000);
```

  
+ MySQLのdatetimeにJavaScriptのDateを合わせる  
また、MySQLをdatetime型でテーブル定義してしまいましたので、これまた変換しなければいけません。下の参考記事の中で最もシンプルなものを採用です。
  + →[参考][Convert JS date time to MySQL datetime](http://stackoverflow.com/questions/5129624/convert-js-date-time-to-mysql-datetime)

```
var mysqlJpTime = date.toISOString().slice(0, 19).replace('T', ' ');
```

これですべての実装が完了！

### 稼働確認
いつも通り、`npm start` からの `localhost:3000`で一覧から確認。

一覧画面
<img width="1034" alt="一覧画面_v2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/137947/aa9001a0-8011-c744-8a82-676c1acef0f4.png">

詳細画面
<img width="1033" alt="詳細画面_v2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/137947/d5ceba98-81e2-b971-60cd-6051d4dc5651.png">

新規登録画面
<img width="1033" alt="新規登録画面_v2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/137947/dc2c63b1-3c5e-082a-f20a-ea4182fdb224.png">

更新画面
<img width="1034" alt="更新画面_v2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/137947/1d3131c9-1080-31b4-df27-1f713395b4a6.png">

削除確認画面
<img width="1033" alt="削除確認画面_v2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/137947/b15d8f99-cc59-181f-ab59-91a1652b1af7.png">


何とか全ての処理ができたようです(\*'ω'*)

#まとめ
２回に渡ってやってきたExpressを使ったメモ帳アプリの作成から、体感は以下の感じです。

+ ExpressはWebアプリ作成の骨となるルーティングや画面表示までは容易にできる。
    + しかも強力なコード自動生成で雛形を作ってくれるので、必要な部分に手を加えればOK
+ DB等の別の機能との連携まで手を伸ばすと、別途Express外の機能の理解が必要。
    + 裏を返せば、他の機能との連携に制約がなく、自由にカスタマイズ可能とも言えそう。

今回はExpressを扱ってみましたが、他のWebアプリケーションフレームワークも触って比較してみたいと思います。それはまた別の機会に。

*以上*