目的
バックエンドを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を使う場合は適宜読み替えてください)
利用モジュール
{
"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は生成する)
npx express-generator express-generator-babel --no-view --git
2. (作成されたフォルダに移動し)必要なライブラリをインストール
yarn install
3. expressが起動することを確認
yarn start
ブラウザで「http://localhost:3000」 を表示する
4. babelインストール
yarn add @babel/cli @babel/core @babel/preset-env -D
5. babelの設定ファイル(.babelrc)を作成する
targetにcurrentを入れることで、インストールされているnode.jsが理解できる形でトランスパイルされます。
(最近のnodeであればawaitやasyncなどがヘルパー関数ではなく、そのまま出力されます(pollyfill不要))
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
6. babelでビルドするため、フォルダ構成を変更する。
- jsファイルを「/src」フォルダ配下にすべて移動する
mkdir src
mv bin/ routes/ app.js src/
※「bin」「routes」フォルダと、app.jsファイルを「src」フォルダに移動している。
- フォルダ階層が変わったためソースを一部修正する
app.js (publicフォルダの位置を修正)
app.use(express.static(path.join(__dirname, 'public')));
↓
app.use(express.static(path.join(__dirname, '../public')));
- トランスパイル先フォルダを作成する
mkdir dist
7. ビルド用コマンドをpackage.jsonに追加する
package.json の "scripts"に下記コマンドを追加する。
"build": "babel src --out-dir dist --copy-files && babel ./src/bin/www --out-file ./dist/bin/www"
※.src/bin/www は拡張子がないjsファイル。フォルダ指定では対象にならないため、個別にトランスパイルをしている。
8. babelでトランスパイルを行う
yarn build
9. トランスパイルしたソースで動作確認を行う
- package.json の "scripts"に下記コマンドを追加する。
"serve": "node ./dist/bin/www"
- サーバを起動する
yarn serve
ブラウザで「localhost:3000」を開き、expressのトップページが開くことを確認する。
10. ESモジュールに変更して動作確認を行う
- require()をimportに変更しても動作することを確認する。
- module.exportsからES6のexportに変更した場合、読み込み元も「import」に修正する必要がある。
- ソース内でrequire()をimportに変更しても、外部にエクスポートする「module.export」を変更しなければ、呼び出し元は変更不要。
/src/routes/index.js
// 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
// 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モジュール化後の動作確認
yarn build
yarn serve
- ブラウザで「localhost:3000」を開き、expressのトップページが開くことを確認する。
12. 開発用に「babel-node」を導入する(ビルド無しで動作させるため)
- babel-nodeをインストールする
yarn add @babel/node -D
- package.jsonの startを修正する
"start": "node ./bin/www",
↓
"start": "babel-node ./src/bin/www",
- babel-nodeで起動することを確認する(build不要)
yarn start
ブラウザで「localhost:3000」を開き、expressのトップページが開くことを確認する。
②sequelize+sqliteを追加してデータを取得する
sequelize-cliを利用して生成したひな形(node module形式)を、express側から利用できることを確認する。
13. sequelize導入(DBはsqliteを利用する。別にサーバを作る必要がないため)
yarn add sequelize sqlite3
yarn add sequelize-cli -D
- 初期化時に自動生成されるフォルダを「/src」配下にするため、先に「.sequelizerc」ファイルを作成する(プロジェクトルート)
touch .sequelizerc
- sequelize関連ファイルを「/src/seqelize」で管理するように設定する(指定しない場合は、プロジェクトルートに作成される)
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'),
};
- マイグレーションのための初期化を行う
yarn sequelize init
※コマンドは、sequelize, sequelize-cli どちらでもよい(エイリアス)
プロジェクトルートではなく、/src/sequelizeに作成されます。
- config.jsonをsqlite用に書き換える。
{
"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定義と、マイグレーション用のひな形を生成する。
yarn sequelize model:generate --name User --attributes name:string,birth:date,email:string
- マイグレーションを行い、テーブルを作成する
yarn sequelize db:migrate
15. テスト用データを登録(db:seed)する
- ひな形を作成する
yarn sequelize seed:generate --name user
- ひな形を修正(登録データを用意する)
'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を登録する
yarn sequelize db:seed:all
※やり直す場合は「yarn sequelize db:seed:undo:all」
16. 作成したmodelをexpress側から利用し、登録したデータが取得できることを確認する
- ./route/users.jsを修正し、sequelizeのmodel(users)を全レコードjson形式で返すように修正する
修正前
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;
修正後
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)も変更する。
// var usersRouter = require('./routes/users');
import usersRouter from './routes/users';
17. ビルド後、データが取得できることを確認する
yarn build
yarn serve
- localhost:3000/users にアクセスして、DBからデータが取得できることを確認する
[{"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"}]