[Node.js勉強会]ExpressフレームワークでTODOアプリを作ろう

# この記事について
Node.jsの概念や、Expressの使い方について詳しく書いてある記事ではありません。
ある程度の土台が用意してあるので、それを元に実際に手を動かして、TODOアプリを完成させることがこの記事の目的です。

pugファイルと仮処理を記述 の部分までは、コピペで進めていただいても大丈夫です。
TODOの処理を記述　の部分から実際に、Node.jsの様々な書き方を試していただけたらと考えております

今回作成する、TODOアプリの動作は以下のような形です。
![express-comp.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203126/858a53f4-8c50-4337-3562-0110b310f1b4.gif)



# Node.jsとは
> _Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。_
[Node.js公式](https://nodejs.org/ja/)

非同期で処理が実行されるので、DBとの通信のような時間がかかる処理を実行する場合は async, await の記述が必要です。

# Expressとは
> _Node.js のための高速で、革新的な、最小限のWebフレームワーク_
[Express公式](https://expressjs.com/ja/)

最小限の機能を持ったフレームワークです。基本的に利用するのは、ルーティングの機能かと思います。
LaravelやRailsのようなフレームワークの場合、プロジェクトを作成した時点でログイン機能、メール機能、など様々な機能が簡単に実装出来るように用意されていますが、Expressは全て自分でライブラリを選定して実装しないといけません。
そのため、フレームワーク自体の容量がとても小さいです。

# 事前準備
・ Git
・ Docker

# 手順１ dockerを利用して、Expressの実行環境を整える
スムーズにNode.jsの学習を進めるために、開発環境の構築をDockerで行います。

```
$ git clone https://github.com/KazukiSadasue/express-mysql-sample.git
$ cd express-mysql-sample
$ docker-compose up -d
```

上記のコマンドで、dockerコンテナが起動して、サーバーも立ち上がります。
http://localhost
に接続して、以下の画像のように表示されたら実行環境は整いました。
＊もしかしたら、最初の１回目にサーバーの立ち上げ失敗するかもしれません。
その場合は、`docker-compose restart` で再度立ち上げると治ると思います。

<img width="598" alt="express-first-step.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203126/3e53a39c-1617-182a-d013-73e4bdd9e0cd.png">


# 手順２ 作成するアプリケーションについて
簡単なTODOアプリケーションを作成します。
ルーティングは以下のようにしようと思います。

|パス  |メソッド  |内容  |
|---|---|---|
|/  |GET  |TODOトップ  |
|/create  |POST  |TODO作成  |
|/done  |POST  |TODO完了  |
|/delete  |POST  |TODO削除  |

TODOテーブルは以下のようにします。

|カラム  |型  |説明  |
|---|---|---|
|id  |INTEGER  |PRIMARYキー  |
|content  |STRING  |TODO内容（必須）  |
|isDone  |Boolean  |TODOの状態（必須、デフォルト0）  |
|deletedAt  |DATETIME  |削除日時  |
|createdAt  |DATETIME  |作成日時  |
|updatedAt  |DATETIME  |更新日時  |

# モデルの作成
テーブル設計から、TODOモデルを作成します。
modelsフォルダにtodo.jsを作成して、中身は以下のようにします。

<details>
<summary>todo.jsのコード</summary>
<div>

```javascript:todo.js
'use strict';
const loader = require('./sequelize-loader');
const Sequelize = loader.Sequelize;

const Todo = loader.database.define(
  'todos',
  {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true,
      allowNull: false,
    },
    task: {
      type: Sequelize.STRING,
      allowNull: false,
    },
    isDone: {
      type: Sequelize.BOOLEAN,
      allowNull: false,
      defaultValue: 0,
    },
  }, {
    paranoid: true,
    freezeTableName: true,
  }
);

module.exports = Todo;
```

</div>
</details>

次にapp.jsでモデルを読み込みます。

```diff:app.js
var logger = require('morgan');
var helmet = require('helmet');

// モデルの読み込み
+var Todo = require('./models/todo');
+Todo.sync();
```

モデルの作成が終わったら、Dockerコンテナを再度立ち上げます。

```
$ docker-compose restart
```

サーバーが再起動して、モデルに対応するテーブルが作成されます。
テーブルを確認するには、以下のコマンドを実行

```
$ docker exec -it mysql bash
# mysql -u root -p
Enter Password: パスワードは入力せずにEnter

mysql> use express-sample
mysql> show tables;
+--------------------------+
| Tables_in_express-sample |
+--------------------------+
| todos                    |
+--------------------------+
```

todosテーブルが作成されているのが確認出来ました！

# pugファイルと仮処理を記述
TODOアプリのデザインはBootstrapを用いて作成しました。
以下のコードをコピペして配置してください。

<details>
<summary>views/index.pugのコード</summary>
<div>

```views/index.pug
extends layout

block content
  h1 TODOアプリ
  div
  form(action="/create", method="post").form-inline.mb-3
    div.form-group
      input(type="text" name="task" placeholder="例: 晩御飯を作る").form-control
    button(type="submit").btn.btn-primary 追加

  div.mb-3
    h2 TODO
    form(name="todoform" method="post").mb-2
      ul.list-group
      each todo in todoTasks
        li.list-group-item.px-5
          input(name="todos", value=todo.id, type="checkbox", id=`todo-${todo.id}`).form-check-input
          label(for=`todo-${todo.id}`).form-check-label #{todo.task}
    if todoTasks.length
      button.btn.btn-success(onclick="done()").mr-2 達成
      button.btn.btn-danger(onclick="deleteTodo()") 削除
    else
      div TODOタスクはありません


  div
    h2 DONE
    form(name="doneform" method="post").mb-2
      ul.list-group
      each done in doneTasks
        li.list-group-item.px-5
          input(name="todos", value=done.id, type="checkbox", id=`todo-${done.id}`).form-check-input
          label(for=`todo-${done.id}`).form-check-label #{done.task}
    if doneTasks.length
      button.btn.btn-danger(onclick="deleteDone()") 削除
    else
      div DONEタスクはありません

  script(src="javascripts/todo.js")
```

</div>
</details>

<details>
<summary>puglic/javascripts/todo.jsのコード</summary>
<div>

```javascript:public/javascripts/todo.js
function done() {
  document.todoform.action="/done";
  document.todoform.submit();
}

function deleteTodo() {
  document.todoform.action="/delete";
  document.todoform.submit();
}

function deleteDone() {
  document.doneform.action="/delete";
  document.doneform.submit();
}
```

</div>
</details>

<details>
<summary>routes/index.jsのコード</summary>
<div>

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

// TODOトップページ
router.get('/', async (req, res, next) => {
  const todoTasks = [];
  const doneTasks = []

  res.render('index', {
    todoTasks, doneTasks,
  });
});

module.exports = router;
```

</div>
</details>

コードの記述が終わったら、 `docker-compose restart` コマンドでコンテナを立ち上げなおします。

すると、以下のような状態になるかと思います。
<img width="566" alt="express-second-step.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203126/08329812-0ad2-1c5c-8202-dbf5e84df38d.png">



# TODOの処理を記述
ここまで見た目の部分と、モデルの定義まで終わりました。
あとは、routes/index.js の部分に処理を書き加えるだけでTODOアプリが完成します。
ここから先は、色々と手を動かしてNode.jsの書き方に触れていただけたらと思います。

### 基本的な使い方
```javascript
// ライブラリや、モデルの読み込みには、requireを利用する
const { Op } = require('sequelize');

// getのルーティング
router.get('/', () => {});
// postのルーティング
router.post('/', () => {});
```

### Sequelizeの使い方
モデルの使い方は、 [Sequelize公式ドキュメント](https://sequelize.org/v5/) を参考にします。
使い方の一部を抜粋してこちらに記述します。

```javascript
// async を記述することで、非同期関数を定義する。
router.get('/', async (req, res, next) => {
  // isDoneがtrueのデータを全件取得
  // awaitを記述することで、DB処理の結果が返ってくるまで待つ
  const todos = await Todo.findAll();
})
```

Sequelizeの使う上で、Node.jsは非同期通信であることに注意しないといけません。
async, awaitを利用して、DB処理が終わってから次の処理を行うようにしないと、不具合の原因となってしまいます。

上記の内容を参考に、TODOアプリを完成させていただけたらと思います。

# 最後に
TODOアプリ完成しましたでしょうか？
最終的な私の実装例を記述致します。
<details>
<summary>routes/index.jsの実装例</summary>
<div>

```javascript:routes/index.js
var express = require('express');
var router = express.Router();
const Todo = require('../models/todo');
const { Op } = require('sequelize');

// TODOトップページ
router.get('/', async (req, res, next) => {
  const todos = await Todo.findAll();
  const todoTasks = todos.filter(todo => todo.isDone === false);
  const doneTasks = todos.filter(todo => todo.isDone === true);

  res.render('index', {
    todoTasks, doneTasks,
  });
});

// TODO作成
router.post('/create', async (req, res, next) => {
  await Todo.create({
    task: req.body.task,
  });
  res.redirect('/');
});

// TODO完了
router.post('/done', async (req, res, next) => {
  const todoModels = await getModelsByIds(req.body.todos);
  for(const todo of todoModels) {
    await todo.update({
      isDone: true
    });
  }
  res.redirect('/');
});

// TODO削除
router.post('/delete', async (req, res, next) => {
  const todoModels = await getModelsByIds(req.body.todos);
  for(const todo of todoModels) {
    await todo.destroy();
  }
  res.redirect('/');
});

// リクエストのIDからTODOモデルを取得する
async function getModelsByIds(ids) {
  if (typeof ids === 'string') {
    ids = ids.split();
  }
  return await Todo.findAll({
    where: {
      id: {
        [Op.in]: ids,
      }
    }
  });
}

module.exports = router;
```

</div>
</details>


もっと説明の記述が必要、xxが分からない等ありましたらコメントお願い致します！

今回のコードの配置場所
https://github.com/KazukiSadasue/express-mysql-sample