LoginSignup
11
15

More than 5 years have passed since last update.

SE1年目のJavaScript Webアプリケーションフレームワーク道...Expressで簡易メモ帳アプリ(1)

Posted at

JavaScriptのWebアプリケーションフレームワーク道

SEとしての1年目も終わりに差し掛かり、個人的にやってきたJavaScriptのWebアプリ作りで学んできたことを整理して、アウトプットするために記事にしました。あまりまとまっていませんが、ご容赦を。

今回はExpressについてです。

目標

JavaScriptのWebアプリケーションフレームワークの中で最もシンプルなのが"Express"。
Expressでメモ帳アプリを作成することを目標にします。
記事が長くなりそうだったので、基本編とDB接続編の2本を立てです。
この記事は、基本編として画面表示まで行います。

基本編とDB接続編をまとめた完成版コードはGitHubに置いてありますので、ご自由に御覧ください♪ → sasaken555/expMemo

やること

  • Webアプリケーションフレームワークの利用

やらないこと

  • ES6の記法

実装する要件

基本的なCRUD処理ができれば良いなと。具体的には以下のような感じです。

  1. メモの一覧表示
    1. 最初に表示
    2. 各メモは作成・更新時間も分かるようにする
    3. 各メモは詳細も見れるものとする
  2. メモの登録
    1. タイトルと本文のみ入力する
  3. メモの更新
    1. これもタイトルと本文のみ入力する
  4. メモの削除
    1. 消す前の確認画面を入れる

実行環境

動作確認は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('/', --) の部分
app.js
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接続処理を入れるまで一旦ダミーデータで代用します。

routes/index.js
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)で書いてみます。

views/layout.jade
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の読み込み部分になります。

views/index.jade
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を指定し、そのパラメータを取得する

routes/index.js
/* 詳細画面表示処理 */
// 正規表現(\\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)の記述

views/memoDetail.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 戻る

サーバーから渡された値を素直に表示するだけなんで、一覧画面よりも簡単。

新規登録画面

サーバー側の処理

routes/new.js
var express = require('express');
var router = express.Router();

/* 新規登録画面表示処理 */
router.get('/', function(req, res, next) {
  res.render('newMemo', { pageTitle: '新規メモ作成' });
});

module.exports = router;

これは空のフォーム画面であるnewMemo.jadeを返すだけで良いのでシンプル。

Jadeの記述

views/newMemo.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領域を表示する。これは素直だね。

更新画面

サーバー側の処理

routes/edit.js
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の記述

views/editMemo.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で既に入っている値を表示するようにしました。

削除画面

サーバー側の処理

routes/delete.js
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の記述

delete.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にアクセス!

一覧画面_v1.PNG

上の図のように一覧が表示されて、Fine(*'ω'*)!!
※ データは少し弄ってあるので、多少異なります

ここまでのまとめ

  • Express単体で、ルーティングや画面表示まで実装可能
  • DBに接続せずとも、簡単な表示を行うレベルならExpressは十分手軽にアプリケーションを作成できそう。
    • 特にexpress-generatorによる自動生成は強力

次の記事では、『DB接続編』 としてDBとの連携を行ってみたいと思います。

以上。

11
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
15