JavaScriptだけでWebサイト開発

node.js + Express + mongodb系 + React でサービスを作るメモ

node.js + Express + mongoose で掲示板アプリを作ってみる

前回の「node.js+mongodbで掲示板アプリを作ってみる」ではnode.jsとmongodbのnative driverだけでapp.jsのみで実装しました。

  • 独自ルーティング
  • デザインもHTMLも混在
  • dbのドキュメント構造がわかりにくい
  • その他Webアプリケーションで共通の処理手順をそのつど書く

ため、ソースに手を加えるたびに煩雑になってきます。

目次

0.MVCモデルの掲示板アプリを作る

今回はExpressとmongooseを使用してきれいな構造&少ない手順にします。

Express node.jsのスタンダードなwebアプリケーションフレームワーク

mongoose node.jsとmongodbのためのODMパッケージ

前提となる開発環境はMacOS High Sierra

1. 新しいnodeをインストール

新しくプロジェクトを開始するときはやったほうがいいでしょう

$ nodebrew install-binary stable
Fetching: https://nodejs.org/dist/v9.0.0/node-v9.0.0-darwin-x64.tar.gz
######################################################################## 100.0%
Installed successfully
$ nodebrew use 9.0.0
$ nodebrew migrate-package 8.8.1
$ npm upgrade

やったこと

  • nodebrewをつかって新しいバージョンのnodeをインストール
  • インストールされた最新を use
  • 以前使用したバージョンのグローバル環境に入れていたパッケージを新しいバージョンに migrate
  • パッケージをアップグレード

1. Expressアプリケーションのプロジェクトを作る

npm で express-generator が入っているとexpressコマンドで簡単にひな形をつくってくれます。

 $ express -h
 
   Usage: express [options] [dir]
   Options:
   --version output the version number
   -e, --ejs add ejs engine support
   --pug add pug engine support
   --hbs add handlebars engine support
   -H, --hogan add hogan.js engine support
   -v, --view <engine> add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
   -c, --css <engine>  add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
   --git add .gitignore
   -f, --force force on non-empty directory
   -h, --help  output usage information

オプション含めてどんなviewエンジンを利用するか決めます。今回は標準のpugを使います。 てきとうなディレクトリで

$ express -v pug kakiapp

   create : kakiapp
   create : kakiapp/package.json
   create : kakiapp/app.js
   create : kakiapp/public
   create : kakiapp/routes
   create : kakiapp/routes/index.js
   create : kakiapp/routes/users.js
   create : kakiapp/views
   create : kakiapp/views/index.pug
   create : kakiapp/views/layout.pug
   create : kakiapp/views/error.pug
   create : kakiapp/bin
   create : kakiapp/bin/www
   create : kakiapp/public/javascripts
   create : kakiapp/public/images
   create : kakiapp/public/stylesheets
   create : kakiapp/public/stylesheets/style.css

   install dependencies:
     $ cd kakiapp && npm install

   run the app:
     $ DEBUG=kakiapp:* npm start

$ cd kakiapp
$ npm install
~
$ DEBUG=kakiapp:* npm start
> kakiapp@0.0.0 start /Users/haranaga/node/kakiapp
> node ./bin/www

  kakiapp:server Listening on port 3000 +0ms

やったこと

  • expressプロジェクトのファイルを指定したディレクトリ内に全部そろえてくれる
  • 作られたディレクトリに移動してローカルに依存パッケージをnpm install
  • サーバーを起動

ブラウザでの動作

  • localhost:3000/ にアクセスすると「Express Wellcome to Express」と表示される

できたファイル群

├── app.js              アプリ本体
├── bin/                起動スクリプト置き場所
├── node_modules/       依存パッケージ
├── package-lock.json   いつもの
├── package.json        いつもの
├── public/             静的ファイルを公開する場所
├── routes/             コントローラーの置き場所
└── views/              Viewテンプレートの置き場所

これでnode.js+Expressの環境が整いました。 生成されたアプリケーションは / アクセスで上記の表示 /users へのアクセスで respond with a resource とtextを表示するだけのシンプルなものです。

デフォルトで設定される機能

  • path: ローカルディレクトリのパス文字列のコントロール
  • serve-favicon: faviconの設定を任せる
  • morgan: ログ記録
  • cookie-parser: cookie制御
  • body-parser: POST Bodyを読み込んでくれる

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');

var index = require('./routes/index');
var users = require('./routes/users');
var up = require('./routes/up');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
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')));

app.use('/', index);
app.use('/users', users);

// 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;

2. mongooseをインストールして使えるようにする

プロジェクトのディレクトリ内で

$ cd kakiapp
$ npm install mongoose --save

3. mongoose接続とSchema定義を追加する

ソースを変更

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');

var index = require('./routes/index');
var users = require('./routes/users');
var up = require('./routes/up'); // 投稿画面

var app = express();

//// 1. mongoose connection
var mongoose = require('mongoose');  // mongoose 利用
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/app1', {useMongoClient: true}); // 接続
//// end 1

//// 2. mongoose model 定義
// Schema 定義
var postSchema = mongoose.Schema({
    kakikomi: String,
    name: String
});
// model
var Post = mongoose.model('Post',postSchema);
// Post modelを router moduleで共有する
app.set('Post',Post);
//// end 2

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
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')));

app.use('/', index);
app.use('/users', users);

//// 3. 投稿画面
app.use('/up', up);
//// end 3

// 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;

やったこと コメント //// 1 〜 3 を追加しました。

  1. mongoose でmongodb接続
  2. mongoose スキーマ定義と掲示板データを保存するコレクションのモデル定義
  3. route /up を投稿画面として追加

4. 投稿画面を追加

先にapp.jsapp.use('/up',up);を追加しました。

    ├── routes
    │   ├── index.js
    │   ├── up.js      * 投稿画面コントローラー
    │   └── users.js 
    └── views
        ├── error.pug
        ├── index.pug
        ├── layout.pug
        ├── up.pug        * 投稿フォーム
        └── up_post.pug   * 投稿後ページ

これらを追加します

routes/up.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
    res.render('up');
});

router.post('/', (req,res,next) => {
    var Post = module.parent.exports.get('Post');
    var post = new Post();
    post.kakikomi = req.body.kakikomi;
    post.name = req.body.name;
    var promise = post.save();
    promise.then((doc) => {
        res.render('up_post',doc);
    });
});

module.exports = router;

やったこと

  • /up/ の GETリクエストで view/up.pug を表示する
  • /up/ への POSTリクエストで mongodbの postsコレクションへデータを追記して追加したデータをviewに渡して、view/up_post.pug を表示する

投稿フォーム

views/up.pug

extends layout

block content
    h2 投稿フォーム
    form(action="/up" method="POST")
        p 内容:
        textarea(name="kakikomi" style="width:100%;height:60px;")
        p 名前:
        input(type="text" name="name")
        div(style="margin-top:30px;")
            button(type="submit") 投稿

views/up_post.pug

extends layout

block content
    h2 投稿しました
    p #{kakikomi}
    p #{name}

ブラウザでの動作

  • /up で フォームを表示
  • 投稿ボタンで「投稿しました」画面を表示

5.トップページを書き込み一覧表示にする

トップページ / を書き込まれたデータの一覧表示画面にします. コントローラー routes/index.js と ビューの views/index.pug を編集します

routes/index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
    var Post = module.parent.exports.get('Post');
    Post.find().sort({_id:-1}).exec((err, posts) => {
        res.render('index', { title: '書き込み一覧', posts: posts });
    });
});

module.exports = router;

views/index.pug

extends layout

block content
    h2= title
    style ul {list-style:none;} .name {font-size:0.8em;color:#555;} .box {border:1px solid #888;padding:20px;margin-bottom:40px;}
    ul
        each post in posts
            li
                .box
                    div #{post.kakikomi}
                    p(class="name") #{post.name}

やったこと

  • / のGETリクエストでmongodbのコレクションpostsから全件データを_id降順で取得します。
  • 取得したデータをviewのposts変数に渡して views/index.pugを表示します

ブラウザでの動作

  • トップページにアクセスすると先に投稿した書き込みを一覧表示します。

6. まとめ

  1. express-generator パッケージのコマンドライン express で雛形を作成
  2. mongodbを利用したオブジェクトモデリング mongoose をnpmインストール
  3. app.js にmongoose利用の設定と接続
  4. app.js に mongoose Schemaの Post モデルを定義(kakikomiフィールドとnameフィールド)
  5. 投稿画面用に /up ルートを追加
  6. 投稿画面機能を routes/up.jsに フォーム表示とPOSTでデータ保存の処理
  7. トップページ / を 書き込み一覧ページとしデータを取得して表示する

検討事項

プロジェクトの各機能をファイルとディレクトリに分けて構造化するにあたって、セッション共通の依存性注入(Dependency Injection)をexpressでどのように実現するかいろいろと方法があるようです。
今回はエントリーポイントの app.js内で mongoose.model を app.set()し、呼び出される各モジュールで module.exports.get()して参照する方法で実装しました。

以上でnode.js + Epress + mongoose を使って簡単な掲示板サービスができました。

©ichi-bit