Webアプリ開発のオーソドックスな戦術とは 〜Express.js, MVC, Mustache, テンプレートエンジン
プログラム自学案内の22回目です。今回はWeb MVCフレームワークを用いたアプリの基本を紹介します。なお、これまでの記事はこちらです。
Express.jsを導入する
今回からExpress.jsを使い始めます。Express.js は、Web MVC フレームワークの1つです。それって、どういうものなのでしょうか? 説明だけで分かるものではないので、サンプルを動かしてみましょう。
まずはプロジェクトを作成して、express, mustache-express, morganという 3つのパッケージをインストールします。
mkdir flatter-web
cd flatter-web
npm init -y
npm install express mustache-express morgan
サンプルコードの全体像
つぎに、ファイルをどんどん追加してゆきます。まずは追加後の最終形を画像でお見せします。
node_modules, package-lock.json, package.json は すでに作られているはずですので、追加するファイルは残り、すなわち画像でC,M,V,設定という矢印が伸びている部分、あわせて、7つのファイルです。
初めての Express.js サンプルにしては、ずいぶんとファイル数が多い感じがしますが、これには理由があります。
MVCアーキテクチャ : Webサーバアプリ開発の戦術
サンプルにしては数が多い理由は、この記事ではMVCアーキテクチャという戦術を紹介したいからです。これを紹介するには、どうしてもある程度の数のファイルが必要になります。
このことは、サッカーとかラグビーなどのプレーヤー数が多い球技で、フォーメーション研究が盛んであることと、関係があると思っています。サッカーやラグビーはルール上決められていなくても、ポジションを分けて分業体制をしきます。そして、どう分業すれば勝てるか、その研究はいつまで経っても研究しつくせない感じだと思います。
Webサーバのプログラミングにもこれに似たところがあって、コードを小分けにして、うまく役割分担をさせると、上手に作れることが多いのです。そして、Webサーバのプログラミングにおける、古典的、かつ、今日でもオーソドックスな戦術が、MVCアーキテクチャと呼ばれるものなのです。
MVCアーキテクチャは文字通り、次のポジションに分けてフォーメーションを組みます。
M : Model(モデル)
V : View(ビュー)
C : Controller(コントローラ)
ポジション別に、その役割とサンプルコードを紹介していきます。
M: Model
Modelは、サービスにおける、本質的な処理や計算をする役割を担います。たとえばGoogleなら検索、メルカリなら古物売買取引のマッチングといった処理が、Modelとして書かれます(彼らがMVCアーキテクチャを用いていれば、ですが)。
ここにはどんな処理も書けますが、Web、HTTP、HTMLに関する処理が一切書かれていないことに注意してください。そのことこそが、MVCアーキテクチャのツボの1つです。
今回のサンプルのアプリが提供するサービスは「おだてる(flatter)」ことなので、その処理がここには書かれます。
models/flatter.js
function flatter(name) {
const praise_words = ['素敵', 'スマート', 'オシャレ'];
const flatter_words = [];
for (word of praise_words) {
flatter_words.push(`${name}さんは${word}ですね`);
}
praise_words.forEach(word => {
flatter_words.push(`${name}さんは本当に${word}ですね`);
});
for (let i = 0; i < praise_words.length; i++) {
flatter_words.push(`${name}さんはつくづく${praise_words[i]}ですね`);
}
return { flatteree: { name: name }, words: flatter_words };
}
module.exports = flatter;
models/namelist.js
function namelist() {
return { namelist: ['桐島かれん', '滝沢カレン'] };
}
module.exports = namelist;
V: View
Viewは、Modelによる処理結果をHTTPレスポンスに変換する役割を担います。
MVCフレームワークを利用すると、この役割は、HTMLの雛形(テンプレート(template) と言います)を作るだけで済んでしまいます。テンプレートを準備しておけば、次のことはMVCフレームワークがしてくれるからです。
テンプレートエンジンを使って、HTMLテンプレートに値を埋め込む
値が埋め込まれたHTMLをもとにHTTPレスポンスを作る
今回のサンプルでは Mustacheテンプレートエンジンを利用します。Mustacheは {{ xxxx }} と2連続中かっこで囲われた文言を解釈し、そこに値を埋め込んでくれます。どんなふうに解釈し、どう埋めてくれるかはここで試せます。
views/flatter_view.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/stylesheets/style.css">
<title> {{ flatteree.name }} さん</title>
</head>
<body>
<h1> {{ flatteree.name }} さんって !!! </h1>
{{ words }}
<hr>
<a href="/">ホームへ</a>
</body>
</html>
views/list_view.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/stylesheets/style.css">
<title> Hello flatter-web </title>
</head>
<body>
<h1> Hello flatter-web !!! </h1>
<ul>
{{ #namelist }}
<li><a href="flatter/{{.}}">{{.}}をおだてる</a></li>
{{ /namelist }}
</ul>
</body>
</html>
static/stylesheets/style.css
body {
padding: 50px;
font: 14px sans-serif;
}
a {
color: #00B7FF;
}
C: Controller
Controllerは、HTTPリクエストを受け取り、ModelとViewの選択・呼び出しをすることにより、HTTPレスポンスを応答する役割を担います。
Modelの選択、すなわちサービスにおける本質的な処理の選択は、HTTPリクエストの Path(=URLの右のほうの部分)の違いで振り分けるのが一般的です。Pathにより処理を振り分けることを ルーティング(routing) といい、ルーティングをする部品を ルータ(router) と言います。
したがって、コントローラには、HTTPリクエスト の Path ごとに Model を選択し、処理を呼び出し、最後にViewを選択し、ViewによりHTTPレスポンスを作成するコードを書いていくことになります。次のサンプルでは、「'/'」「'/flatter/:name'」という二つのPathに対してそのコードを書いています。
controllers/router.js
const express = require('express');
const router = express.Router();
const flatter = require('../models/flatter');
const namelist = require('../models/namelist');
router.get('/', function (req, res, next) {
const data = namelist();
console.log(data);
res.render('list_view.html', data);
});
router.get('/flatter/:name', function (req, res, next) {
const data = flatter(req.params.name);
console.log(data);
res.render('flatter_view.html', data);
});
module.exports = router;
アプリケーション設定
アプリケーション設定は、WebサーバアプリやMVCの設定をする役割を担います。この役割は、MVCの外側にあるポジションです。
他のコードがHTTPリクエストへの応答のたびに動くのとは対照的に、ここに書かれるコードはWebサーバ起動時にしか動きません。
Controllerの役割が「受け取ったボールを、グラウンド上の誰にパスするか」を選択する選手になぞらえられるなら、アプリケーション設定の役割は「この試合では、どの選手を起用し、どういうフォーメーションを組むか」を選択する監督の役割になぞらえられるかもしれません。このポジションこそ、ある意味一番難しい役割かもしれませんね。
app.js
const express = require('express');
const mustacheExpress = require('mustache-express');
const logger = require('morgan');
const router = require('./controllers/router');
const app = express();
app.engine('html', mustacheExpress());
app.set('views', __dirname + '/views');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(__dirname + '/static'));
app.use('/', router);
app.listen(3000, () => { console.log("Listening on port 3000...") });
動かしてみる
全てのサンプルコードのコピペがおわったら、次のコマンドを実行してください。Webサーバが起動します。
node app.js
http://127.0.0.1:3000/ をブラウザで開き、リンクをクリックしたりして、動きを確認してみてください。
Ctrl(Command) + C で終了させます。
コードの肉付け部分を改造する
Express.js などの フレームワーク (Framework) と呼ばれるライブラリを使うと、理屈はさておき、骨組みのコードに対して何となくのノリで 肉付けしてゆくことによって、骨組みにそってプログラミングをすることができます。
この 「理屈はさておき」「何となくのノリで」できることの是非は、なかなか意見が分かれるところ ですが、楽しいのでちょっと試してみましょう。 つぎの課題はすべて「肉付け部分」の改造です。
なお、.jsファイルや.htmlファイルを変更したら、いちどWebサーバを終了させ、再起動させなければいけないことに注意してください(なお、この再起動を自動化するためのnpmパッケージもあります。この記事では扱いませんが、ぜひ調べて導入してみてください)。
では課題を3つ出します。
課題1 View の部分を改造
View の部分を書きかえて、おだてている画面(桐島、滝沢両方)の見栄えをちょっと改善してみましょう。
こうなっているところを、
こうしたいです。
どのファイルを、どう修正すればよいでしょうか?
課題2 Model の部分を改造
Model の部分を書きかえて、トップページに桐島かれん、滝沢カレンに加えて「カレン・カーペンター」をおだてるページへのリンクを、加えてください。
課題3 Controllerの部分を追加、Model の部分も追加
ブラウザで http://127.0.0.1:3000/ にアクセスすると、カレンをおだてるページへのリンクの一覧が表示されます。では、http://127.0.0.1:3000/richard にアクセスしたときには、リチャードをおだてる一覧(リチャード・カーペンター、リチャード・ギア、キース・リチャード)が表示されるようにするには、どうすればいいでしょうか?
Controllerの部分と、Modelの部分への追加が必要になります。Viewの修正や追加は不要のはずです。
コードを辿り、サンプルコードの意味を調べる方法
さて、さきほどボソッとこのように書きました。
本当は、サンプルコードの理屈が、理解できていた方がいいのです。
ですので、今回のサンプルコードのなかで、理屈が納得いかない、不思議な部分があれば、その意味をぜひ調べてみてください。いきなり全部を完全に理解しようとするのはシンドイので、まずは、気になるところ、1つ、2つ、というふうに始めれば良いでしょう。
なぜいきなり全部を完全に理解しようとするのがシンドイかというと、今回は、express, mustache-express, morgan という 3つのパッケージを使ったサンプルですので、この3つのパッケージの取扱説明書と格闘しなければいけないからです。
たとえば controllers/router.js の
router.get('/flatter/:name', function (req, res, next) {
const data = flatter(req.params.name);
});
この1行目 router.get('/flatter/:name', function (req, res, next) の書き方と意味については、Express.jsの取説を読む必要があります。なぜこの部分はExpress.jsと分かるのでしょう? それは、routerという語を上に辿ると
const express = require('express');
const router = express.Router();
とあるので、これはExpress.jsの機能を呼び出しているということが分かるわけですね。
では、2行目 flatter(req.params.name), は何をやっているのでしょうか?
flatter の意味は
const flatter = require('../models/flatter');
とあるので、サンプルの models/flatter.js ファイルを読むことになります。 一方、req.params.name ですが、そもそもこの頭のreq がどこからきているかというと、
router.get('/flatter/:name', function (req, res, next)
のところから来ているので、このreq.params.nameの意味や使い方は、Express.js の ここ や ここ に書かれているということになります。残念ながら、英語です。
そこを探し当てるにも、探しあてたその箇所を読み解くのにも、はじめは相当な時間がかかると思います。こればかりは、少しずつ慣れていくしかありません。
もちろん Express.js は、日本人にも使われていますので、「Express.js router.get」とかで google検索 すると何かしらの日本語記事はヒットします。ですが、その記事に自分の知りたいことが書かれているかどうかは、場合によるという感じです。
ここらへんの難しさ、面倒くささに耐えられるかどうかが、「プログラミングが得意かどうか」「プログラミングを仕事にするための最低限の適性があるか」のひとつの境目と言えます。
まとめと次回予告
今回の記事では、Express.jsのサンプル、MVCアーキテクチャ、Mustacheテンプレートエンジンを紹介しました(盛りだくさんでしたね)。
前々回あたりから、ぐっと難易度は上がっています。そのかわり、作られたものの動きの面白さ、MVCというカラクリの面白さは、実際に触れた人にしか味わえない、喜びだと思うのです。この喜びを、読者にも味わっていただければと思います。
次回の記事では、今回の記事の課題の解答例とともに、HTMLの formタグを用いた、ブラウザとWebサーバとの双方向のやり取りを紹介します。
#コラム #プログラミング #独学 #案内 #Express.js #Mustache #MVCアーキテクチャ
この記事が気に入ったらサポートをしてみませんか?