目的
WEB アプリケーションの入門として下記を学ぶことが目的です。
- Express スタートアップ (Node.js 初期化含む)
- O/R マッパー sequelize を使った DB 処理入門
- React スタートアップ
今回は Express スタートアップと sequelize の利用方法について記載してます。
尚、自分の理解を整理する目的で記載するため、チュートリアル形式で記載してませんので、かいつまんで読んで頂けると幸いです。
おまけとして、記事の最後に VSCode 開発者向けデバッグ方法を記載しました。
尚、Windows において動作確認してますがコマンドは Linux の shell 形式で表示しますので、Windows ユーザは読み替えるようお願いします
技術スタックについての概要
- Node.js は主にサーバサイドの JavaScript プラットフォームである
- Express は Node.js のフレームワークである
- MVC デザインパターンに従ってアプリケーションをコーディングできる
- sequelize は O/R マッパーである (O/R マッパーは Object と DB のリレーションをマッピングするもの)
- sequelize は
mysql
,sqlite
,postgres
,mssql
をマッピングできる - sequelize-cli を使うと DB マイグレーションができる
- sequelize は
- React はインタラクティブな UI を作るための JavaScript ライブラリである
※ 技術スタックを選定した理由は GROWI(旧crowi-plus) の開発に commit するためである。
Express スタートアップ(windows user 向け)
Express とは
Express は Node.js のフレームワークである。
Express は、早い・独断的でない・ミニマリストフレームワークであるとうたっている。
Node.js はサーバサイド(クライアントサイドでも使える)の JavaScript プラットフォームである。
今回は Node.js を知らない前提で(自分がそうなので) Express を扱う。
Express を含め、Node.js 上のフレームワークは Node Frameworks にまとまっている
Express インストール (windows user 向け)
Express を動作させるには Node.js をインストールする必要がある。
Node.js のバージョン管理が出来る nodist を使うと楽なので、nodist をインストールする。
- nodist インストール
- https://github.com/marcelklehr/nodist/releases から exe をダウンロードしてインストールする
- コマンドプロンプトで
nodist -v
でバージョンが表示されたらインストール成功
- Node.js のインストール
-
nodist global 8
を実行する -
node -v
で Node.js のバージョンが表示されたらインストール成功
-
- Express のインストール
-
npm install express --save
を実行する - 参考情報: http://expressjs.com/ja/starter/hello-world.html
-
Node.js アプリケーションの初期化
Express アプリケーションは Node.js アプリケーションでもあるため、まず、Node.js アプリケーションとしての初期化を行う。
初期化には npm init
を実行する。
インタラクティブモードでアプリケーションの基本情報(名前やバージョン)を聞かれるので適宜答える。
このとき、公開アプリケーションとする場合、アプリケーション名はユニークである必要がある。(ローカルで利用するだけであれば気にしないでよい)
(参考情報: https://docs.npmjs.com/files/package.json)
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (app)
version: (1.0.0) 0.0.1
description: Sample
entry point: (index.js)
test command:
git repository: https://localhost/express-sample_app.git
keywords:
author: tatsurou
license: (ISC) MIT
About to write to /home/hoge/usr/express-sample_app/app/package.json:
{
"name": "app",
"version": "0.0.1",
"description": "Sample",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://localhost/express-sample_app.git"
},
"author": "tatsurou",
"license": "MIT",
"bugs": {
"url": "https://localhost/express-sample_app/issues"
},
"homepage": "https://localhost/express-sample_app#readme"
}
Is this ok? (yes)
これで package.json ファイルが作成される。
package.json にはアプリケーションが利用する(アプリケーションを起動するために依存する)パッケージ情報が記載される。
アプリケーションを実行する際はこの情報を元にして、必要なパッケージをインストールすることになる。
以降、開発を進めるにあたって必要なパッケージが増えてきたら package.json にパッケージを追加していけばよい。
package.json へのパッケージの追加方法
$ npm install XXX --save
※詳細は npm install --help
を参照
Express アプリケーションの初期化
Express アプリケーションを初期化するためには、express-generator をインストールすると楽である。
(※ Express(express) はアプリケーションを実行するために必要なもの。一方で express-generator は CLI から Express アプリケーションのスケルトンをコマンド1発で作成するものであり動作に必須ではない)
$ npm install express-generator -g
$ express --view=pug myapp
Express アプリケーションの階層構造
express --view=pug myapp
により作成されたディレクトリ構造は次のとおりである。
Express アプリケーションの基本的なディレクトリ構造となる。
myapp
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.pug
├── index.pug
└── layout.pug
ディレクトリ | 説明 |
---|---|
app.js | Expressアプリケーション本体 |
bin | ディレクトリ内にwwwファイルがあり、wwwにはapp.jsをサーバとして動作させるための記述がされている。 |
public | 静的コンテンツを格納するディレクトリ |
package.json | NPM パッケージの名前や、依存する他パッケージ名、パッケージの実行スクリプトを定義するファイル。npm start で実行できるようにするためには、このファイルの scripts に記載する。 |
routes | ルーティングコードを記述した JS ファイルを格納するディレクトリ |
views | MVC モデルのアプリケーションにおける View の役割を持つファイルを格納するディレクトリ。.pug はテンプレートエンジンの1つ Pug(旧Jade) で記述されたファイル。 |
Express と Node.js の橋渡しとなる bin/www
bin/www には Node.js を使って Express アプリケーションを WEB サーバとして実行するための橋渡しとなる記述がされている。
下記が bin/www の内容となっており、var app = require('../app');
で Express を使って記述した app.js 読み込み、var server = http.createServer(app);
でサーバとして実行している。
その他、Listen するポート番号の指定や、エラーが発生した時の処理が記述されている。
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('app:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
: <snip>
Express は HTTP モジュールの createServer に渡せるよう、express()
が呼び出されると function(req, res, next)
を返す。
Express アプリケーション作成
Express のインストールとアプリケーションスケルトンの作成が終わったので、アプリケーションの作成に移っていく。
静的ファイルのルーティング設定方法
まずは静的ファイルを表示させるよう作成していく。
が、アプリケーションスケルトンを作成すると既に app.js に静的ファイルを表示するためのルーティング設定が行われているので、記述箇所を確認しながら設定方法を記述する。
app.js には書かれている下記箇所が静的ファイルのルーティング設定である。
app.use(express.static(__dirname + 'public'));
意味するところは、public 配下にあるディレクトリを静的コンテンツに指定することである。(より詳細を知りたい場合は express.static を参照のこと)
アプリケーションスケルトンには、画像(public/images)、JavaScript(public/javascripts)、StyleSheet(public/stylesheets) があるため、HTTP パス /images/*
, /javascripts/*
, /stylesheets/*
にアクセスると静的コンテンツを表示することが出来る。
'public'
は node を実行したカレントディレクトリからの相対パスなので、__dirname + 'public'
等と、絶対パスで指定するほうが望ましい(※)とのこと。
※ http://expressjs.com/en/starter/static-files.html
__dirname はカレントモジュールのパスに展開される。(参考情報:Node.js docs > __dirname)
複数の静的コンテンツ用パスを指定する場合
app.use(express.static(__dirname + 'public'));
app.use(express.static(__dirname + 'protected'));
任意の HTTP パスを指定してルーティング設定を行う場合
app.use('/static', express.static(path.join(__dirname, 'public')));
Express の仕組み
Express は役割の異なるいくつかのミドルウェアにより構成されている。
ミドルウェアは Express のメソッドとしてアクセスして利用することが出来る。
(例: ルータ・レベルのミドルウェアを利用する場合は express.Router()
を実行した戻り値がルータ・レベルのミドルウェアを指すオブジェクトとなる)
ミドルウェアの種別
Express アプリケーションはいくつかの種別のミドルウェアを利用することが出来る。
ミドルウェアの種別は次のとおり。
- アプリケーション・レベルのミドルウェア
- ルータ・レベルのミドルウェア
- エラー処理ミドルウェア
- 標準装備のミドルウェア
- サード・パーティー・ミドルウェア
アプリケーション・レベルのミドルウェアと、ルータ・レベルのミドルウェアはオブジェクトを取得する方法が異なるものの、動作としては同様にルーティング設定を行う。(ルーティングについては後述)
エラー処理ミドルウェアは、エラーが発生した時の処理を記述し、標準装備のミドルウェアは Express 4.x において express.static
, express.json
, express.urlencoded
を指す。
サード・パーティー・ミドルウェアは Express 標準ではないサード・パーティーが作成したミドルウェアを指す。
詳細は Express > Express 4 への移行 を参照のこと。
ルーティング
ルーティングとは、HTTP アクセス時に対して、ソフトウェアのエントリーポイントを設定し、設定に従って処理を行うことを指す。
例えば、ブラウザで http://localhost/
へアクセスすると HTTP パス /
に対する GET メソッドによる要求が WEB アプリケーションに送信される。
この要求に対して次のようなルーティングを設定していた場合、WEB アプリケーションは http://localhost/test
に対するアクセスに対して、テキスト Hello World
を応答する。
app.get('/test', function(req, res) {
res.send('Hello world.');
});
Express におけるルーティング
Express におけるアプリケーション・レベルのミドルウェアや、ルータ・レベルのミドルウェアによりルーティングを制御する。
Express アプリケーション・レベルのミドルウェア又は Express のルータ・レベルのミドルウェアが持つメソッド get
、post
、put
、delete
により設定する。
(先の「/testへアクセスするとHello_worldを応答する例」では、app.get
により GET メソッドを処理している)
※ function(req
, res
) の req は HTTP Request を示すオブジェクトであり、res は HTTP Response を示すオブジェクトである。
参考情報: http://expressjs.com/en/4x/api.html#req , http://expressjs.com/en/4x/api.html#res
要求応答サイクルは res.send
を実行することで終了する。もし要求応答サイクルを終了させない場合は、next()
を実行する必要がある。app.get
で複数の function を指定した場合は、next()
により次に指定した function の実行に移る。他の app.get
で指定した function に移りたい場合は、next('route')
を指定する。
: <snip>
app.get('/test',
function(req, res, next) {
console.log('Time:', Date.now());
if(Date.now() > 1518884643822) // 02/18より後に実行した場合(時刻指定もあるが説明は割愛)
next('route');
else
next();
},
function(req, res, next) {
res.send('Hello world.');
});
app.get('/test', function(req, res, next){
res.send('Hello special world.');
});
: <snip>
ルーティング設定をモジュール化する
操作したい Model のファイル名を作成して、ルーティング設定をモジュール化する場合、次のように記述する。ここで、routers/
テンプレートエンジン
ルーティング設定に応じた HTML 出力を行う際、テンプレートエンジンを利用することになる。
テンプレートエンジンとは HTML 等の文字列を出力する際、複数のページで共通する部分の共通化や、動的なデータを出力するための処理を行う仕組みのことである。
テンプレートエンジンでは、フロー制御構文も利用できる。
テンプレートエンジンの具体例として、pug(旧: Jade), ejs がある。
※詳細は追記予定。
pug(旧Jade) について
公式サイト:https://pugjs.org/
- Express が標準で利用する HTML テンプレートエンジン
- テンプレート xxx.pug ファイルを views 配下に用意すると、 views/index.pug であれば
res.render('index', { title: 'Express' })
と記述すると HTML 結果を返すことが出来る - pug ファイルは YAML のようにインデントの階層により HTML のネスト構造を表す
- 閉じタグは記載しない
- HTML タグ名のみ記述し、
<>
は記述しない - 親子関係をインデントの階層で表す ※下記例を参照
- if, for, each 等の制御構文を使うことが出来る
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
h1= "Employees"
each employee in employees
dl
dt "name"
dd= employee.name
dt "department"
dd= employee.department
dt "gender"
dd= employee.gender
dt "birth"
dd= employee.birth
dt "joined_date"
dd= employee.joined_date
dt "payment"
dd= employee.payment
dt "note"
dd= employee.note
O/Rマッパー Sequelize による DB 連携
Sequelize インストール
Sequelize を使うためには、Sequelize 本体(sequelize) と操作するライブラリ用に用意された dialect(mysql
|sqlite
|postgres
|mssql
) をインストールする。
$ npm install sequelize --save
$ npm install mysql2 --save
上記により npm パッケージのインストールと package.json へ sequelize, mysql2 が追加されるようになり、npm install
でインストールされるようになる。
DB マイグレーション
参考情報:Node.jsのSequelizeでDBのmigrationを実行する
参考情報:Sequelize > Migrations
DB マイグレーションは CLI 操作により行う。
そこで、Sequelize を CLI 操作するために sequelize-cli をインストールする。
$ npm install sequelize-cli --save
DBマイグレーションには、DBへの接続設定を記述するためのconfig.jsonと、DBのスキーマを定義するファイルとマイグレーション処理用のファイルが必要となる。
sequelize-cli ではそれらのファイルが下記ディレクトリ構造で作成される。
ディレクトリ名 | 説明 |
---|---|
config | DB へ接続するための DB の URL 、アカウント、パスワード、データベース名を記述する config.json ファイルが格納されるディレクトリ |
migrations | DB マイグレーション用処理用のスクリプトを格納するディレクトリ |
models | DB の OR マッピングされたモデルの定義を記述するファイルを格納するディレクトリ。models ディレクトリ配下のモデルを読み込む index.js が作成される。(後述) |
seeders | テスト目的で利用するDBデータ(seed)を作成するためのスクリプトファイルが格納されるディレクトリ。 |
$ node_modules/.bin/sequelize init
Sequelize CLI [Node: 8.9.4, CLI: 4.0.0, ORM: 4.33.4]
Created "config/config.json"
models folder at "/home/hoge/express-sample_app/app/models" already exists.
Successfully created migrations folder at "/home/hoge/express-sample_app/app/migrations".
Successfully created seeders folder at "/home/hoge/express-sample_app/app/seeders".
{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
DB が存在しない場合は、適宜 MySQL を用意して DB を作成し、特権設定を行う。
そこで設定した内容に合わせて config.json の username
, password
, database
, host
を設定する。
開発環境やテストのために localhost で DB を用意するためには、Vagrant や Docker を使うと楽である。Vagrant や Docker を使う方法は Vagrant (プロバイダは Virtual Box) と Ubuntu で始める Docker / Docker Compose 入門 に記述してある。
- Docker を使う場合は、Docker hub から libraly/mysql, phpmyadmin/phpmyadmin をインストールしてテーブルを作成すると楽
モデルの作成
モデルは sequelize model:create
コマンドで作成できる。
コマンド実行時に属性を attributes で指定することもできる。
使用できるデータ型はSequelizejs > DataTypesを参照のこと。
$ node_modules/.bin/sequelize model:create --name employee --underscored --attributes name:string,department:string,gender:enum,birth:date,joined_date:date,pay:bigint,note:string
Sequelize CLI [Node: 8.9.4, CLI: 4.0.0, ORM: 4.33.4]
New model was created at /models/employee.js .
New migration was created at /migrations/20180218054406-employee.js .
上記コマンドにより models/employee.js
と migrations/$(実行時刻)-employee.js
が作成される。
employee.js が O/R マッピングされたオブジェクト定義ファイルであり、$(実行時刻)-employee.js が DB をマイグレーションする際に使用するファイルである。
尚、モデルを新規作成する場合は気にする必要はないが、DB マイグレーションファイルは可逆になっていることを意識するとよい。
DB マイグレーションファイルに書かれた up
が DB マイグレーション実行時に行われる DB 操作の定義であり、down
が DB マイグレーションの Undo 実行時に行われる DB 操作の定義である。
DB マイグレーションによりモデルを作成した後にアプリケーションが正常に動作しない場合等、DB を元に戻したい事態が起こりうる。その場合、マイグレーションファイルに逆操作を記述(又は自動で逆操作が分かるよう記述)しておけば、DB の Undo をコマンド操作で行うことが出来る。(参考情報:Sequelizejs > Migration > Undoing Migrations)
また、作成されたマイグレーションファイルを見ると、属性に指定していない id, created_at, updated_at が定義されていることが分かる。
これは O/R マッパーが DB のレコードを操作するために必要となる属性である。
(DB の更新可否を updated_at がメモリ内と DB 内で比較して書き込み要否を判断する等)
マイグレーションを実行してモデルをDBに作成する
作成したDBマイグレーションファイルに従ってDBにモデルを作成するには sequelize db:migrate
コマンドを実行する。
--env
でマイグレーションを行う環境を指定できる。
$ node_modules/.bin/sequelize db:migrate --env development
Sequelize CLI [Node: 8.9.4, CLI: 4.0.0, ORM: 4.33.4]
Loaded configuration file "config/config.json".
Using environment "development".
sequelize deprecated String based operators are now deprecated. Please use Symbol based operators for better security, read more at http://docs.sequelizejs.com/manual/tutorial/querying.html#operators node_modules/sequelize/lib/sequelize.js:242:13
== 20180218054406-create-employee: migrating =======
== 20180218054406-create-employee: migrated (0.029s)
マイグレーション実行後のDBデータ |
---|
MySQLのテーブルを作成する際は、照合順序が意図したものであるか注意する。(参考情報:MySQL > 10.4.1 照合順序の実装タイプ) |
想定していた構造と違ったら、一度 DB マイグレーションを Undo してからマイグレーションファイルを修正して再度マイグレーションを行うとよい。
(ちゃんと Undo 出来るか確認も兼ねられるので想定通りであっても Undo はした方がよいだろう)
アプリケーション上でのモデル操作
sequelize-cli でモデルを作成すると models/index.js が作成され、中に DB 設定(config/config.json) に従って DB と接続して models ディレクトリ配下のモデル定義を読み込むよう記述されている。
アプリケーションでは models/index.js を読み込むと OR マッピングされたモデルを JS のオブジェクトとして扱うことが出来る(※下記例参考)
require('../models');
と記述すれば models/index.js が読み込まれる。(参考情報: https://nodejs.org/dist/latest-v8.x/docs/api/modules.html#modules_folders_as_modules)
var models = require('../models');
exports.index = function(req, res, next) {
models.Employee.all().then(employees => {
res.render('employee/index', {employees : employees});
});
};
'use strict';
module.exports = (sequelize, DataTypes) => {
var Employee = sequelize.define('Employee', {
name: DataTypes.STRING,
department: DataTypes.STRING,
gender: DataTypes.ENUM('male', 'female', 'other'),
birth: DataTypes.DATE,
joined_date: DataTypes.DATE,
payment: DataTypes.BIGINT,
note: DataTypes.STRING
}, {
underscored: true,
freezeTableName: true, // cf. https://stackoverflow.com/questions/21114499/how-to-make-sequelize-use-singular-table-names
});
Employee.associate = function(models) {
// associations can be defined here
};
return Employee;
};
モデルの属性値更新による DB の更新
OR マッパーにより、モデルの属性値を更新するとモデルオブジェクト(メモリ内)の値の更新と DB の値の更新が行える。
Sequelize で利用できるメソッドには次のものがあり、保存されたデータの読み込みは .find
系、新規作成する際は .build
, .create
、保存・更新・削除する際は .save
, .update
, destroy
が使える。(下記表を参照)
(いずれにせよ各メソッドを使う前に .define
でモデルクラスの定義が行われている必要がある)
モデルクラスのメソッドについての詳細は Sequelize > Models Usage ページを参照のこと。
また、インスタンスのメソッドについての詳細は Sequelize > Instance ページを参照のこと。
関数名 | 説明 |
---|---|
>> | <モデルクラスに対して実行するメソッド> (参考情報: Sequelize > Models Usage) |
.build |
メソッドの引数にて属性値を指定して、モデルのインスタンス(DBのレコード)を新規追加する。 永続化(DBへの保存)は自動では行われないため、任意のタイミングで明示する必要がある。 新規追加時に属性値が指定されない場合は定義された初期値が代入される。 DBへ保存する場合は .build メソッドの戻り値となるインスタンスの .save メソッドを呼び出す。 |
.create |
モデルのインスタンスを新規追加する。 永続化が自動で行われる。 |
>> | <モデルインスタンスに対して実行するメソッド> (参考情報: Sequelize > Instance) |
.update |
メソッドの引数にて属性値を指定して、モデルのインスタンスを更新し、更新した後のインスタンスを永続化する。 |
.save |
モデルのインスタンスを永続化する。 |
.destroy |
モデルのインスタンスを削除する。 DBのレコードも削除される。 モデルが paranoid を有効にするよう定義していた場合は削除操作の代わりに deletedAt に削除時刻が記録される。この場合でも .destroy({ force: true}) を実行すれば削除が行われる。 |
.reload |
モデルインスタンスを DB からリロードする。.reload メソッドを実行した時の DB データで上書きされるため、.save される前にモデルインスタンスへ行った変更内容は消失する。 |
.increment |
属性の値をインクリメントする |
.decrement |
属性の値をデクリメントする |
>> | <モデルクラスに対して実行する複数インスタンス操作を行うメソッド> |
.bulkCreate |
モデルのインスタンスを複数新規作成する。 永続化が自動で行われる。 .bulkCreate メソッドは戻り値として作成したモデルインスタンスの配列を返す。 |
React編に続く。
(おまけ) Express のデバッグ
Express アプリケーションをデバッグする方法を記述する。
VSCode でデバッグするには launch.jsonファイル を指定する
VSCode ではデバッグモードでアプリケーションを起動する方法が用意されている。
デバッグモードでは、ブレークポイントの設定、ステップ実行や、変数の中身の確認等が行える。
デバッグモードで実行するためには launch.json ファイルにアプリケーション実行方法を定義する。
launch.json ファイルを自動生成するには、左ペイン操作一覧から「虫マーク」ボタン をクリックした後、画面上部の▶ボタン横のプルダウンメニューから「構成を追加する」を選択して「{} Node.js: プログラムの起動」を選択する。
launch.jsonの自動生成画面 |
---|
操作方法 1. 左ペイン操作一覧から「虫マーク」ボタン をクリック 2. 画面上部の▶ボタン横のプルダウンメニューから「構成を追加する」を選択 3. 「{} Node.js: プログラムの起動」を選択 |
下記は、/app/bin/www
をデバッグモードで起動するための設定「Launch Program」を記述した例である。
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/app/bin/www"
}
]
}
デバッグモードで起動するには、左ペイン操作一覧から「虫マーク」ボタン をクリックした後、画面上部の▶ボタンを押す。後は、一般的なデバッグツールと同様に操作できる。
React編に続く。
本記事に対する編集リクエストを受けて修正した履歴とお礼
編集リクエストを頂いた皆様に感謝致します。
- sch923 さんより React 編へ続く旨を記載するようリクエストを頂きました
- tetuwo_5555 さんよりコマンドパスの修正リクエストを頂きました