expressをESモジュールに変更する手順(babel利用)

# 目的
バックエンドをESモジュールで記載できるようにしたい(フロントエンドに合わせる)という目的です。

* おまけにsequelizeを追加して、動作することも確認する。

* 完成後ソース(tagのv1がsequelize追加前まで、v2(最新コミット)が最後まで)
https://github.com/murasuke/express-generator-babel

## 結果
* ESMとしてexportしたモジュールはimportする、module.exportの場合(node)はrequire()を利用するということに気を付ければ、混在していても問題なく動く。

## 実現手段
* expessをES6(import)で利用できるようにするため、babelでトランスパイルする。
⇒package.json に 「type: module」を追加すればESモジュールを使えますが、jsファイルの拡張子(.cjs、.mjs)の切り分けが面倒なのでbabelにします。

* express-generatorでひな形を生成する。
* 開発時は「babel-node」で実行(ビルド不要)可能とする。トランスパイルしたjsファイルが必要な場合はbuildを行う。
* sequelizeと組み合わせて使用する(sequelizeはnodeモジュール形式で生成される。ES6形式で記載したexpress側で問題なく利用できることを確認する）

### 前提
* nodeとyarnがインストール済みであること
(npmを使う場合は適宜読み替えてください）


## 利用モジュール

```json
{
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "morgan": "~1.9.1"
  },
  "devDependencies": {
    "@babel/cli": "^7.12.10",
    "@babel/core": "^7.12.10",
    "@babel/node": "^7.12.10",
    "@babel/preset-env": "^7.12.11"
  }
}
```
# 手順

## ①expressのひな形をESモジュールに変更し、babelでコンパイルする
#### 1. express-generatorでひな形を作成(viewは生成しない。.gitignoreは生成する)
```bash
npx express-generator express-generator-babel --no-view --git
```
#### 2. (作成されたフォルダに移動し)必要なライブラリをインストール
```bash
yarn install
```
#### 3. expressが起動することを確認
```bash
yarn start
```
![B397.tmp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/e22625e2-7934-3db4-9a95-23ad51c1b290.png)

ブラウザで「http://localhost:3000」 を表示する
![515.tmp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/ae4ad89b-1623-ff87-902e-2f91d2a881f6.png)



#### 4. babelインストール

```bash
yarn add @babel/cli  @babel/core @babel/preset-env -D
```
#### 5. babelの設定ファイル(.babelrc)を作成する
targetにcurrentを入れることで、インストールされているnode.jsが理解できる形でトランスパイルされます。
(最近のnodeであればawaitやasyncなどがヘルパー関数ではなく、そのまま出力されます(pollyfill不要)）

```json
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "targets": {
                    "node": "current"
                }
            }
        ]
    ]
}
```
#### 6. babelでビルドするため、フォルダ構成を変更する。
* jsファイルを「/src」フォルダ配下にすべて移動する

```bash
mkdir src
mv  bin/ routes/ app.js src/
```
※「bin」「routes」フォルダと、app.jsファイルを「src」フォルダに移動している。


![変更前後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/a28254fd-48e2-19a7-ba55-97bcf8e3df60.png)


* フォルダ階層が変わったためソースを一部修正する

app.js (publicフォルダの位置を修正)

```bash
app.use(express.static(path.join(__dirname, 'public')));
 ↓
app.use(express.static(path.join(__dirname, '../public')));
```

* トランスパイル先フォルダを作成する

```bash
mkdir dist
```

#### 7. ビルド用コマンドをpackage.jsonに追加する

package.json の "scripts"に下記コマンドを追加する。

```json
"build": "babel src --out-dir dist --copy-files && babel ./src/bin/www --out-file ./dist/bin/www"
```
※.src/bin/www は拡張子がないjsファイル。フォルダ指定では対象にならないため、個別にトランスパイルをしている。

#### 8. babelでトランスパイルを行う
```bash
yarn build
```


#### 9. トランスパイルしたソースで動作確認を行う

* package.json の "scripts"に下記コマンドを追加する。

```json
"serve": "node ./dist/bin/www"
```

* サーバを起動する

```bash
yarn serve
```

ブラウザで「localhost:3000」を開き、expressのトップページが開くことを確認する。
![515.tmp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/c0aec8cd-0f9a-0914-6786-07476348fa43.png)


#### 10. ESモジュールに変更して動作確認を行う

* require()をimportに変更しても動作することを確認する。

1. module.exportsからES6のexportに変更した場合、読み込み元も「import」に修正する必要がある。
1. ソース内でrequire()をimportに変更しても、外部にエクスポートする「module.export」を変更しなければ、呼び出し元は変更不要。

/src/routes/index.js

```javascript
// var express = require('express');  //コメントアウト
import express from 'express';        //追加
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
    res.render('index', { title: 'Express' });
});

// module.exports = router;  //コメントアウト
export default router;       //追加
```

/src/app.js

```javascript
// var express = require('express');  //コメントアウト
// var path = require('path');
// var cookieParser = require('cookie-parser');
// var logger = require('morgan');

import express from 'express';        //追加
import path from 'path';
import cookieParser from 'cookie-parser';
import logger from 'morgan';

// var indexRouter = require('./routes/index');  //コメントアウト
import indexRouter from './routes/index';        //追加
var usersRouter = require('./routes/users');

var app = express();

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

module.exports = app;  //コメントアウトしない。読み込み元(bin/www)を変更せず、require()のままとするため。
//export default app;  // exportに書き換えると、読み込み元をimportに書き換える必要がある。

```

#### 11. ESモジュール化後の動作確認
 
```bash
yarn build
yarn serve
```

* ブラウザで「localhost:3000」を開き、expressのトップページが開くことを確認する。

#### 12. 開発用に「babel-node」を導入する（ビルド無しで動作させるため）

* babel-nodeをインストールする

```bash
yarn add @babel/node -D
```
* package.jsonの startを修正する

```json
"start": "node ./bin/www",
　↓
"start": "babel-node ./src/bin/www",
```

* babel-nodeで起動することを確認する(build不要)

```bash
yarn start
```
ブラウザで「localhost:3000」を開き、expressのトップページが開くことを確認する。

## ②sequelize＋sqliteを追加してデータを取得する
sequelize-cliを利用して生成したひな形(node module形式)を、express側から利用できることを確認する。

#### 13. sequelize導入(DBはsqliteを利用する。別にサーバを作る必要がないため）
 
```bash
yarn add sequelize sqlite3
yarn add sequelize-cli -D
```

* 初期化時に自動生成されるフォルダを「/src」配下にするため、先に「.sequelizerc」ファイルを作成する(プロジェクトルート)

```bash
touch .sequelizerc
```

* sequelize関連ファイルを「/src/seqelize」で管理するように設定する（指定しない場合は、プロジェクトルートに作成される）

```javascript

const path = require('path');

module.exports = {
    'config': path.resolve('./src/sequelize/config', 'config.json'),
    'models-path': path.resolve('./src/sequelize/models'),
    'seeders-path': path.resolve('./src/sequelize/seeders'),
    'migrations-path': path.resolve('./src/sequelize/migrations'),
};
```
* マイグレーションのための初期化を行う

```bash
yarn sequelize init
```
※コマンドは、sequelize, sequelize-cli どちらでもよい（エイリアス）

プロジェクトルートではなく、/src/sequelizeに作成されます。
![sequelize-init.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/1aac19f7-a2ed-5863-d173-474fe3e92628.png)


* config.jsonをsqlite用に書き換える。

```json
{
  "development": {
    "database": "database_development",
    "dialect": "sqlite",
    "storage":"db/proto_app_dev.sqlite"
  },
  "test": {
    "database": "database_test",
    "dialect": "sqlite",
    "storage":"db/proto_app_test.sqlite"
  },
  "production": {
    "database": "database_production",
    "dialect": "sqlite",
    "storage":"db/proto_app.sqlite"
  }
}
```

#### 14. sequelize-cli でテーブル定義のひな形を作成後、マイグレーションを行う

* sequelize model:generateでmodel定義と、マイグレーション用のひな形を生成する。
 
```bash
yarn sequelize model:generate --name User --attributes name:string,birth:date,email:string
```
migrationsにひな形が生成されます。
![sequelize-generate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/ce3ccceb-472c-81b2-d5da-7fc177b36df2.png)

* マイグレーションを行い、テーブルを作成する

```bash
yarn sequelize db:migrate
```
![db-migrate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276579/cbf76e00-ad5b-3957-b648-5e4e59c2e787.png)

#### 15. テスト用データを登録(db:seed)する

* ひな形を作成する
 
```bash
yarn sequelize seed:generate --name user
```

* ひな形を修正(登録データを用意する)

```javascript
'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    const now = new Date();
    const birth = new Date(now);
    const seeds = []; 
    const subtractYear = ( date, year ) => new Date( date.setYear(date.getFullYear() - year) );

    seeds.push({ name:"name1", birth:subtractYear(birth, 1), email:"mail1@example.com", createdAt: now, updatedAt: now });
    seeds.push({ name:"name2", birth:subtractYear(birth, 1), email:"mail2@example.com", createdAt: now, updatedAt: now });
    seeds.push({ name:"name3", birth:subtractYear(birth, 1), email:"mail3@example.com", createdAt: now, updatedAt: now });
    return await queryInterface.bulkInsert("users",seeds, {});
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('users', null, {});
  }
};
```

* seedを登録する


```bash
 yarn sequelize  db:seed:all
```
※やり直す場合は「yarn sequelize  db:seed:undo:all」


#### 16. 作成したmodelをexpress側から利用し、登録したデータが取得できることを確認する
 
* ./route/users.jsを修正し、sequelizeのmodel(users)を全レコードjson形式で返すように修正する

修正前

```javascript
var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
    res.send('respond with a resource');
});

module.exports = router;
```

修正後

```javascript
import db from "../sequelize/models/index";
import express from 'express';
const router = express.Router();

/* GET users listing. */
router.get('/', async function(req, res, next) {
  //res.send('respond with a resource');
  const users = await db.User.findAll();
  res.json( users );
});

// module.exports = router;
export default router;

```

呼び出し元(app.js)も変更する。

```javascript
// var usersRouter = require('./routes/users');
import usersRouter from './routes/users';
```

#### 17. ビルド後、データが取得できることを確認する
 
```bash
yarn build
yarn serve
```

* localhost:3000/users にアクセスして、DBからデータが取得できることを確認する

```json
[{"id":1,"name":"name1","birth":"2020-01-10T09:51:38.737Z","email":"mail1@example.com","createdAt":"2021-01-10T09:51:38.737Z","updatedAt":"2021-01-10T09:51:38.737Z"}
,{"id":2,"name":"name2","birth":"2019-01-10T09:51:38.737Z","email":"mail2@example.com","createdAt":"2021-01-10T09:51:38.737Z","updatedAt":"2021-01-10T09:51:38.737Z"}
,{"id":3,"name":"name3","birth":"2018-01-10T09:51:38.737Z","email":"mail3@example.com","createdAt":"2021-01-10T09:51:38.737Z","updatedAt":"2021-01-10T09:51:38.737Z"}]
```