JavaScriptのWebアプリケーションフレームワーク道
SEとしての1年目も終わりに差し掛かり、個人的にやってきたJavaScriptのWebアプリ作りで学んできたことを整理して、アウトプットするために記事にしました。あまりまとまっていませんが、ご容赦を。
今回はExpressについてです。
目標
JavaScriptのWebアプリケーションフレームワークの中で最もシンプルなのが"Express"。
Expressでメモ帳アプリを作成することを目標にします。
記事が長くなりそうだったので、基本編とDB接続編の2本を立てです。
この記事は、基本編として画面表示まで行います。
基本編とDB接続編をまとめた完成版コードはGitHubに置いてありますので、ご自由に御覧ください♪ → sasaken555/expMemo
##やること
- Webアプリケーションフレームワークの利用
##やらないこと
- ES6の記法
実装する要件
基本的なCRUD処理ができれば良いなと。具体的には以下のような感じです。
- メモの一覧表示
- 最初に表示
- 各メモは作成・更新時間も分かるようにする
- 各メモは詳細も見れるものとする
- メモの登録
- タイトルと本文のみ入力する
- メモの更新
- これもタイトルと本文のみ入力する
- メモの削除
- 消す前の確認画面を入れる
#実行環境
動作確認はWindows10、GoogleChrome から実施しています。
以下、メインで使う環境たちです。
名称 | バージョン | 内容 |
---|---|---|
Node.js | 7.4.0 | JavaScriptのランタイム |
NPM | 3.10.9 | パッケージを管理する |
Express | 4.14.0 | Webアプリケーションフレームワーク |
MySQL | 5.7 | リレーショナルデータベース |
前提
Node.js と MySQLはローカルにインストールしています。
#実装
プロジェクト作成
フォルダ分けって自分でやると統一性がなくてグチャグチャになりがちですが、
Expressは公式から雛形プロジェクトを作るCLI(= express-generator)があります。
express-generatorのインストール方法は公式に譲りますが、あとは下3行をターミナルから叩きます。
$ express expMemo
$ cd expMemo
$ npm install
これで雛形完成!たったの3分で、すごく簡単!!
画面表示とルーティング
Node.jsとExpressの心臓部分となるapp.js
から手を加えてみます。
心臓部分
サーバー側からどのURLアクセスでどの処理を行うか(=ルーティング)や、エラーハンドリングを行うapp.js
を編集します。基本的なコードは自動生成されるので、以下のルーティング部分を変えればOK...すごい親切ですね!
-
require('./routes/--')
の部分 -
app.use('/', --)
の部分
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
// ルーティング用のjsを設定
var index = require('./routes/index');
var add = require('./routes/new');
var edit = require('./routes/edit');
var del = require('./routes/delete');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// ルーティング用のjsとURLを紐づける
app.use('/', index);
app.use('/new', add);
app.use('/edit', edit);
app.use('/delete', del);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
一覧画面
ルーティングはapp.js
とは別にroutes/index.js
を編集します。
index.jsに関しても、基本的なルーティング処理の雛形・お手本が自動生成されているので、処理を記述するのは手軽!
データはDB接続処理を入れるまで一旦ダミーデータで代用します。
var express = require('express');
var router = express.Router();
/* 一覧画面表示処理 */
router.get('/', function(req, res, next) {
/* memoItems: 全てのメモ */
var memoItems = [
{id: 1, title:"initial", created:"2016-02-16 15:58:58", modified:"2016-02-17 11:20:59"},
{id: 2, title:"メール送付", created:"2016-04-01 08:38:49", modified:""},
{id: 3, title:"ツールお試し", created:"2016-11-22 23:01:25", modified:"2016-11-23 23:01:25"}
];
res.render('index', { pageTitle: 'メモ一覧', memoItems: memoItems });
});
module.exports = router;
ここはres.render
部分でテンプレートエンジンであるJadeファイルと、そこに渡す値( { pageTitle: 'メモ一覧', memoItems: memoItems }
部分)を記述することで、/
にアクセスした際に一覧画面を返すようにしています。
テンプレートエンジン
ExpressではテンプレートエンジンとしてJadeを使います。テンプレートエンジンはサーバーから渡された値を基にテンプレートをHTMLに描画(レンダリング)してくれます。
また、UIはみんな大好きBootStrap...にしようかと思いましたが、ここは新しいことへのチャレンジとしてFoundationを使います。個人的にフラットな印象と色使いが好きです。
一覧画面をJade(と Foundation)で書いてみます。
doctype html
html
head
title メモ帳超特急
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.0/css/foundation.min.css')
link(rel='stylesheet', href='/stylesheets/style.css')
body
div
block header
block content
script(src='https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.0/js/foundation.min.js')
ここではhref='云々'
と記述しているlink要素がFoundationの読み込み部分になります。
extends layout
block header
div.top-bar
div.menu-text メモ帳超特急
block content
div.row
h1= pageTitle
div.small-12.columns
a(href="/new").button 新規作成
table
thead
tr
th タイトル
th 作成日
th 更新日
th
th
tbody
- each memo in memoItems
tr
td
a(href="/#{memo.id}") #{memo.title}
td= memo.created
td= memo.modified
td
a(href="/edit/#{memo.id}").button 編集
td
a(href="/delete/#{memo.id}").alert.button 削除
extends layout
部分はlayout.jade
を継承して内容を当てはめるということです。
#{}
の部分はサーバーから渡されたその値を埋め込む処理。
それにしても、うーん。。。キモイ笑
階層構造で表現しているから、HTMLのタグよりも関係性が分かりやすい。あまり触れたことないけれども、Pythonとかはインデントで表現するから近い印象。
each...in...で配列を一つずつ取り出すのも直感的で良い点ですね。
詳細画面
サーバー側の記述
詳細画面も書いてみます。
URLとしては/123
といった/
直下へのアクセスになるので、index.js
に追記します。
ここで引っかかったのは、ルーティングの:memoId
のバリデーション。
そのままURLを書いていると、新規登録画面のURL /new
にアクセスしたときにここにアクセスされてしまいました。
そこで、正規表現(\d+)を加えることで『memoIdが数字ならばここへ』とExpressに指示します。
参考記事→Expressでrouterで正規表現を用いてURLを指定し、そのパラメータを取得する
/* 詳細画面表示処理 */
// 正規表現(\\d+)で 新規登録画面のURL /new とルーティングを区別している
router.get('/:memoId(\\d+)', function(req, res, next) {
var memoId = req.params.memoId;
var targetMemo = {id: 1, title:"initial", created:"2016-02-16 15:58:58", modified:"2016-02-17 11:20:59"};
res.render('memoDetail', { pageTitle: 'メモ詳細', targetMemo: targetMemo });
});
画面(Jade)の記述
extends layout
block header
div.top-bar
div.menu-text メモ帳超特急
block content
div.row
h1= pageTitle
hr
div.small-12.columns
table
tr
td
strong タイトル
td= targetMemo.title
tr
td
strong メモ本文
td= targetMemo.memo
tr
td
strong 作成日
td= targetMemo.created
tr
td
strong 更新日
td= targetMemo.modified
a(href="/").secondary.button 戻る
サーバーから渡された値を素直に表示するだけなんで、一覧画面よりも簡単。
新規登録画面
サーバー側の処理
var express = require('express');
var router = express.Router();
/* 新規登録画面表示処理 */
router.get('/', function(req, res, next) {
res.render('newMemo', { pageTitle: '新規メモ作成' });
});
module.exports = router;
これは空のフォーム画面であるnewMemo.jadeを返すだけで良いのでシンプル。
Jadeの記述
extends layout
block header
div.top-bar
div.menu-text メモ帳超特急
block content
div.row
div.small-12.columns
h1= pageTitle
hr
form(method="post", action="/new")
label タイトル
input(type="text", name="title")
label メモ本文
input(type="text", name="memo")
button(type="submit").button 登録
a(href="/").secondary.button 戻る
タイトルと本文のinput領域を表示する。これは素直だね。
更新画面
サーバー側の処理
var express = require('express');
var router = express.Router();
/* 更新画面表示処理 */
router.get('/:memoId', function(req, res, next) {
var memoId = req.params.memoId;
var targetMemo = {id: 1, title:"initial", created:"2016-02-16 15:58:58", modified:"2016-02-17 11:20:59"};
res.render('editMemo', { pageTitle: 'メモ更新', targetMemo: targetMemo });
});
module.exports = router;
Jadeの記述
extends layout
block header
div.top-bar
div.menu-text メモ帳超特急
block content
div.row
div.small-12.columns
h1= pageTitle
hr
form(method="post", action="/edit/#{targetMemo.id}")
label タイトル
input(type="text", name="title", placeholder="#{targetMemo.title}")
label メモ本文
input(type="text", name="memo", placeholder="#{targetMemo.memo}")
button(type="submit").button 更新
a(href="/").secondary.button 戻る
書き方は新規登録画面とほぼ同じ!
ユーザビリティを意識して、placeholderで既に入っている値を表示するようにしました。
削除画面
サーバー側の処理
var express = require('express');
var router = express.Router();
/* 削除確認画面表示処理 */
router.get('/:memoId', function(req, res, next) {
var memoId = req.params.memoId;
var targetMemo = {id: 1, title:"initial", created:"2016-02-16 15:58:58", modified:"2016-02-17 11:20:59"};
res.render('deleteMemo', { pageTitle: 'メモ削除', targetMemo: targetMemo });
});
module.exports = router;
Jadeの記述
extends layout
block header
div.top-bar
div.menu-text メモ帳超特急
block content
div.row
div.small-12.columns
h1= pageTitle
hr
div.callout.alert
p 本当にこのメモを削除しますか?
table
tr
td タイトル
td= targetMemo.title
tr
td メモ本文
td= targetMemo.memo
hr
form(method="post", action="/delete/#{targetMemo.id}")
button(type="submit").button.alert 削除
a(href="/").secondary.button 戻る
これは詳細画面と似ていますね。
CRUD全ての画面が書けたので、完成!!
稼働確認
以下のコマンドをターミナルから叩いてアプリケーションを起動!
npm start
ブラウザから、localhost:3000
にアクセス!
上の図のように一覧が表示されて、Fine(*'ω'*)!!
※ データは少し弄ってあるので、多少異なります
ここまでのまとめ
- Express単体で、ルーティングや画面表示まで実装可能
- DBに接続せずとも、簡単な表示を行うレベルならExpressは十分手軽にアプリケーションを作成できそう。
- 特にexpress-generatorによる自動生成は強力
次の記事では、『DB接続編』 としてDBとの連携を行ってみたいと思います。
以上。