Express.js の使い方【3】MVCでサイトを構成する

express2-2.png

第2回からの続きです

5.MVCアーキテクチャー

今回は、ExpressのスケルトンをベースにしてMVCアーキテクチャを構成しています。ご存知のようにMVCアーキテクチャーとは、ユーザーインターフェースを持つアプリケーションケーションの実装パターンのひとつで以下の3つから構成されます。

  • Model (データやロジック)
  • View (ユーザーインターフェース)
  • Controller (入力を受け取りModelとViewをコントロールする)

Expressは制約の少ない柔軟なフレームワークなので、スケルトンをベースに様々な機能を加えていくときに、どのようにでも構成できてしまいます。そのような状況で、MVCは設計に明確な指針を与えてくれる存在でもあります。

5.1 スケルトンとMVC

Expressジェネレータが作成するスケルトンは下図のような構成でした。スケルトンの初期状態からMVCの「V」に相当するものは存在しています。

Screen Shot 2019-06-25 at 11.07.47.png

次に、Sequelize-CLIを使ってModelを作成した結果、下図のような状態になりました。(静的ファイルの図は省略します。)

Screen Shot 2019-06-25 at 11.37.27.png

この段階でMVCの「V」と「M」に相当するものができました。Contorollerはまだありませんが、その代わりにRouteのroutes/users.jsがその役割をしています。

5.2 今回のレッスンで作るもの

今回は、「C」のControllerを追加します。Controllerはその名の通り、ModelやViewをコントロールする存在で、Expressに導入する場合はRouteから呼び出してもらうように配置することができます。図解すると下図のようなイメージです。

また、ユーザーを表示するためのviewも新たに追加します。下図のピンク色の長方形の部分が今回追加する部分です。

Screen Shot 2019-06-26 at 15.39.10.png

そして、以下の3つの機能を実装します。

パス 機能
機能1 /users/ 全ユーザーのリストを表示
機能2 /users/:userId ユーザーIDで指定されたユーザーを表示
機能3 /uses/json DBのデータをJSON形式で返す

機能1の表示結果
Screen Shot 2019-07-03 at 8.30.51.png

機能2の表示結果
Screen Shot 2019-07-03 at 8.57.00.png

機能3の表示結果
Screen Shot 2019-07-03 at 8.32.56.png

5.2 MVCに関するディレクトリ

ディレクトリ構成としては、段階を追って以下のように変化します。

Screen Shot 2019-06-19 at 19.40.02.png

5.3 今回のスタート地点

今回のレッスンで新しい機能を実装する前に、現在のコード状況をもう一度整理しておきましょう。これまでのレッスンで./routes/users.jsファイルに2つのルートを追加した結果、現在は以下の3つのルートが存在しています。

パス レスポンス内容
ルート1 /users/ “respond with a resource”と返す
ルート2 /users/:userId “ok”と返す。
console.logでメッセージ出力。
ルート3 /uses/json DBのデータをJSON形式で返す

./routes/users.js全体としては以下のような状態です。現状ではレスポンスを返す処理などもこのファイルに実装されていますので、これらの部分をControllerに移すことにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// 前回のレッスン(第2回)のルート実装
//
var express = require('express');
var router = express.Router();
var dbModels = require('../models/');

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

/*ユーザーIDを指定できるルート*/
router.get('/:userId', function(req, res, next) {
var userId = req.params.userId;
console.log('ユーザーID '+userId+ ' が指定されました。');
res.send('ok');
});

/* ユーザーのリストをJSON出力する */
router.get('/json/', function(req, res, next) {
// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findAll().then(users => {

if (!users) return next(); // 404 Error

res.json(users);
});
});

module.exports = router;

このルーティングを以下のように変更します。

5.4 新しいルーティング実装

上記のように前回のレッスンでは./routes/users.jsにレスポンスを返す処理が実装されていましたが、これを、シンプルにコントローラを呼び出す形に変えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// 今回のレッスン(第3回)のルート実装
//
var express = require('express');
var router = express.Router();
var userController = require('../controllers/user');


/* GET users listing. */
router.get('/', userController.showAllUsers);
router.get('/:userId', userController.showUserById);
router.get('/json', userController.sendJson);

module.exports = router;

5.5 Controllerの仕様

今回作成するControllerは、以下の3つのメソッドを持つものとします。

パス メソッド名 レスポンス内容
メソッド1 /users/ showAllUsers Viewを使って全ユーザーのリストを表示する。
メソッド2 /users/:userId showUserById Viewを使って、ユーザー名を表示する。
メソッド3 /uses/json sendJson DBのデータをJSON形式で返す

5.6 Viewの実装

Controllerの実装を行う前に、Viewをあっさりと作ってしまいましょう。./views/フォルダの下に、ファイルを2つ作ります。

Screen Shot 2019-06-26 at 16.59.17.png
  • allUsers.ejs メソッド1用のView。全ユーザーを表示する。
  • oneUser.ejs メソッド2用のView。1人のユーザーを表示する。
Screen Shot 2019-06-26 at 17.15.32.png

先ず、メソッド1用のviewファイルをallUsers.ejsという名前で以下のように作成します。この中で使われているusersという変数は、後ほどController側で用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<!DOCTYPE html>
<html>
<head>
<title>user</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div>全てのユーザーを表示する</div>
<ol>
<% for(var i=0; i < users.length; i++) { %>
<li> <%= users[i].firstName %> <%= users[i].lastName %></li>
<% } %>
</ol>
</body>
</html>

次に、メソッド2用のviewファイルをoneUser.ejsという名前で以下のように作成します。この中で使われているuserという変数に関しても、後ほどController側で用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<title>user</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>

<div>IDで指定されたユーザーを表示する</div>

<%= user.firstName %>  <%= user.lastName %>

</body>
</html>

5.7 Controllerの実装

それでは、Controllerを実装していきます。

Screen Shot 2019-06-26 at 17.32.00.png

先ず、Controllerモジュールの大枠を考えます。3つのメソッドを以下のように実装することにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var userController = {

//
// メソッド 1
// パス: /user/
// レスポンス: 全てのユーザーを表示する
//
showAllUsers: function(req, res, next) {


},
//
// メソッド 2
// パス: /user/:userId
// レスポンス: IDで指定されたユーザーを表示する
//
showUserById: function(req, res, next) {


},
//
// メソッド 3
// パス: /user/json
// レスポンス: DBのデータをJSON形式で返す
//
sendJson: function(req, res, next) {


}
};

module.exports = userController;

余談になりますが、モジュールのメソッドを実装するときのスタイルはいろいろあり、例えば以下のAとBは同じように動作します。

1
2
3
4
5
6
7
8
var foo = {
bar: function(){
//
},
baz: function(){
}
};
module.exports = foo;
1
2
3
4
5
6
7
8
var foo = {};
foo.bar = function(){
//
};
foo.baz = function(){
//
};
module.exports = foo;

また、今回のメソッドはどれもreqresnextの3つのパラメータを受け取るように設計してあります。

1
function(req, res, next){
メソッド 1 : 全てのユーザーを表示する

それでは、/users/に対応する機能から取り組みます。前回のレッスンで、DBのデータをJSON形式で表示する機能を以下のように実装しました。

1
2
3
4
5
6
7
// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findAll().then(users => {

if (!users) return next(); // 404 Error

res.json(users);
});

このコードの中でjsonを返している行を変更します。

1
2
3
4
//変更前
// res.json(users);
//変更後
res.render('allUsers', {users:users});

res.renderはViewエンジンを使ってレンダリングするメソッドです。パラメータは2つあり、1つは使用するビューの名前、もうひとつはビューへ渡す情報です。Viewに渡す情報はJSON形式であれば内容は自由です。

1
res.render("Viewファイル名", {Viewへ渡す情報});

この変更を反映すると、メソッド1は以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
// メソッド 1
// パス: /user/
// レスポンス: 全てのユーザーを表示する
//
showAllUsers: function(req, res, next) {

// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findAll().then(users => {

if (!users) {
console.log("ユーザーデータを取得できませんでした");
res.send('Error');
} else {
res.render('allUsers', { users: users });
}
});
},
メソッド 2 : IDで指定されたユーザーを表示する

メソッド2は、ユーザーIDを受け取って、そのユーザーの情報を表示する処理です。前回のレッスンで学んだように、URL中のユーザーIDはリクエストオブジェクト(req)にセットされています。

http://localhost/users/123

1
2
3
4
router.get('/:userId',function(req, res, next){
var userId = req.params.userId;
//...
});

このようにして取得したユーザーIDを、SequelizeのfindByPk()メソッドに渡して、指定ユーザーのデータを取得します。findByPkPkというのはPrimary Key(主キー)のことです。背後では、SQLのSELECT文が実行されます。

1
dbModels.User.findByPk(userId).then(user => {

前回のレッスンでも触れましたが、Nodeのデータ取得は非同期で、SequelizeはPromiseを返すので、then(funcfiton(user){})という形式で取得データを受け取ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// メソッド 2
// パス: /user/:userId
// レスポンス: IDで指定されたユーザーを表示する
//
showUserById: function(req, res, next) {

var userId = req.params.userId; // ユーザーIDを取得
if (!userId) {
console.log("ユーザーIDを取得できませんでした");
res.send('Error');
} else {
// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findByPk(userId).then(user => {

if (!user) {
console.log("ユーザーデータを取得できませんでした");
res.send('Error');
} else {
res.render('oneUser', { user: user });
}
});
}
},
メソッド 3 : DBのデータをJSON形式で返す

次に3つめのメソッドを実装します。3つめのメソッドは、DBから取得したユーザーのリストをJSONで返すものです。メソッドの中身の処理は前回のレッスンで実装したものと同じものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sendJson: function(req, res, next) {

// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findAll().then(users => {

if (!users) {
console.log("ユーザーデータを取得できませんでした");
res.send('Error');
} else {
res.json(users);
}

});
},

以上3つのメソッドをまとめると、/controllers/user.js全体としては以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//
// /controllers/user.js
//
var dbModels = require('../models/');
var userController = {

//
// メソッド 1
// パス: /user/
// レスポンス: 全てのユーザーを表示する
//
showAllUsers: function (req, res, next) {

// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findAll().then(users => {

if (!users) {
console.log("ユーザーデータを取得できませんでした");
res.send('Error');
} else {
res.render('allUsers', { users: users });
}
});
},

//
// メソッド 2
// パス: /user/:userId
// レスポンス: IDで指定されたユーザーを表示する
//
showUserById: function (req, res, next) {

var userId = req.params.userId; // ユーザーIDを取得
if (!userId) {
console.log("ユーザーIDを取得できませんでした");
res.send('Error');
} else {
// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findByPk(userId).then(user => {

if (!user) {
console.log("ユーザーデータを取得できませんでした");
res.send('Error');
} else {
res.render('oneUser', { user: user });
}
});
}
},
//
// メソッド 3
// パス: /user/json
// レスポンス: DBのデータをJSON形式で返す
//
sendJson: function (req, res, next) {

// Sequelizeのモデルを使ってデータを取得する
dbModels.User.findAll().then(users => {

if (!users) {
console.log("ユーザーデータを取得できませんでした");
res.send('Error');
} else {
res.json(users);
}

});
},

};

module.exports = userController;

アプリを起動して確認する

それではアプリを起動して、3つの機能を確認してみましょう。

1
$ npm start
機能1

ブラウザで次のURLにアクセスします。

http://localhost:3000/users/

以下のようにユーザーの一覧が表示されれば成功です。

Screen Shot 2019-07-03 at 8.30.51.png

機能2

以下のURLにアクセスします。

http://localhost:3000/users/1

機能2の表示結果
Screen Shot 2019-07-03 at 8.57.00.png

機能3

http://localhost:3000/users/json

機能3の表示結果
Screen Shot 2019-07-03 at 8.32.56.png

第3回のまとめ

今回は、第2回のコードをベースにして、MVCの構成を作成しました。第2回のコードでは、Ruotes以下に実装していたロジックを、Controllerの部分にまとめることができました。

Screen Shot 2019-06-26 at 15.39.10.png
writer | 太田直毅
株式会社OTAシステム開発。フルスタックエンジニア。 仙台市生まれ。大学卒業後に米国系通信会社にエンジニアとして就職。 1998年に独立し、WEBアプリケーションの開発を中心に活動。2児の父。趣味は家族キャンプ。
この記事をシェアする