グルチャ

YEOMANを使ってMEAN(MongoDB+Express+AngularJS+Node.js)を作成しよう

YEOMANを使ってMEANを作成する手順と開発チュートリアルを説明する。
MEANとはMongoDB+Express+AngularJS+Node.jsを組み合わせたWebアプリケーションである。
YEOMANとはフレームワークを簡単に作成できるツールで、node.jsのパッケージとして提供されている。
MEANのフレームワークにはangular-fullstakを使う。
※Yeomanで用意されているジェネレーターは沢山有るがMEANを作成できるジェネレーターで一番人気がangular-fullstak
 
ほとんどの手順は下記URLに従う。
YEOMANを使ってMEAN(MongoDB + Express.js + Angular.js + Node.js)のWebアプリケーションを作る
しかし、上記URLはYEOMANのバージョンが古いためか、チュートリアルの内容が現状のバージョンに沿わないため、下記であらためて説明する。
 
なお、ここで想定している開発環境は下記の通り。
Lubuntu(Ubuntu) 14.04
Node.js v4.2.3
yo 1.6.0
MongoDB 3.2.0
※ 上記以外のバージョンではチュートリアルの内容が沿わない場合がある
 

0. 事前準備

まずはangular-fullstakに必要なアプリをインストールする。
 

0.1. Node.jsのインストール

下記URL参照
Ubuntu 14.04 に Node.jsをインストールする
 

0.2. MongoDBのインストール

下記URL参照
Install MongoDB Community Edition on Ubuntu
 

0.3. Rubyとgemのインストール

下記URL参照
UbuntuにRubyGemsをインストールする
※なお、フレームワークにSassを使わない場合はRubyとgemをインストールする必要はない。
 

0.4. Sassのインストール

端末から下記コマンドを実行する

gem install sass

 

0.5. Yeomanのインストール

npm install -g yo grunt-cli bower

※Yeomanとはyo, grunt, bowerを組み合わせたもの
 

0.6. angular-fullstakのインストール

npm install -g generator-angular-fullstack
npm update -g

※yoが1.4.1以上でないとangular-fullstackが動かないのでアップデートすること
 

0.7. node-inspectorのインストール

デバッガを使う場合は下記コマンドを実行する。

npm install -g node-inspector

以上で事前準備は完了した。
次にMEANアプリの開発チュートリアルを説明する。
 

1. MEANアプリの雛形を作成する

yoを使ってMEANアプリを作成する。
アプリを作る場所はユーザーディレクトリの配下にする。
※ちなみにVMWareなどのバーチャルPCでの共有フォルダにはアプリは作れない。アプリ作成時にシンボリックリンクを貼るが、共有フォルダにはシンボリックリンクが貼れないため。
なお、アプリ名はsampleとする。
 

1.1. sampleディレクトリの作成

mkdir sample
cd sample

※ディレクトリ名がそのままアプリ名になる。
 

1.2. MEANアプリを作成する

yo angular-fullstack

なお、アプリの作成はウィザード方式になっており、各設定をダイアログで設定する。
各項目と選択すべき項目を列挙する。(▼が選択すべき項目 ●はチェックボックスの選択状態 ○はチェックボックスの非選択状態)

[?] What would you like to write scripts with? 
▼Babel
 TypeScript

 

[?] What would you like to write makeup with?
▼HTML
 Jade

 

[?] What would you like to write stylesheets with?
 CSS
▼Sass
 Stylus
 Less

※今回のチュートリアルではスタイルシートは弄らないのでSass以外を選んでも問題ない
 

[?] What Angular router would you like to use?
 ngRoute
▼uiRouter

 

[?] Would you like to include Bootstrap? Yes

 

[?] Would you like to include UI Bootstrap? Yes

 

[?] What would you like to use for data modeling?
●Mongoose (MongoDB)
○Sequelize (MySQL, SQLite, MariaDB, PostgreSQL)

 

[?] Would you scaffold out an authentication boilerplate? Yes

 

[?] Would you like to include additional oAuth strategies?
●Google
●Facebook
●Twitter

※今回のチュートリアルでは関係ないので何を選んでも良い
 

[?] Would you like to use socket.io? Yes

 

[?] What would you like to write tests with?
▼Jasmine
 Mocha + Chai + Sinon

 

1.3. 動作確認する

grunt serve

ブラウザが自動的に起動して下図の画面が表示されれば成功である。
yeoman.01.01
 

2. routerを作成する

yoで作られたMEANアプリにメンバー一覧ページを追加するというチュートリアルを説明する。
処理の流れとしては、
1.yoコマンドでrouterの雛形を作成する
2.routerのhtmlを編集する
3.routerのcontrolerを編集する
という流れになる。
 

2.1. ページを追加する

Ctrl + Cでgruntを止めて、下記のコマンドを実行する。

yo angular-fullstack:route member

下記のダイアログは全てデフォルトにする。
[?] Where would you like to create this route? client/app/
[?] What will the url of your route be? member
すると下記のファイルが作成される。
create client/app/member/member.js
create client/app/member/member.controller.js
create client/app/member/member.controller.spec.js
create client/app/member/member.html
create client/app/member/member.scss
 
動作確認する。

grunt serve

http://localhost:9000/memberにアクセスして、下図が表示されれば成功である。
yeoman.01.02
 

2.2. htmlを編集する

client/app/member/member.htmlを下記の通り編集する。

編集前
<div class="col-md-12">
This is the member view.
</div>

 

編集後
<h2>メンバー一覧</h2>
<ul>
  <li ng:repeat="member in members">
    {{member.name}}
  </li>
</ul>

 

2.3. ダミーデータを追加する

メンバーが正しく表示されるかを確認するためにダミーデータを追加する。(ダミーデータを後からmongoDBのデータに置き換える)
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.message = 'Hello';
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.members = [{name:'田中'},{name:'鈴木'}];
  });

 

2.4. 動作確認する

http://localhost:9000/memberにアクセスして、下図の通り表示されれば成功である。
yeoman.01.03
 

3. clientからserverをGETする

処理の流れとしては、
1.clientからserverの/api/membersをGETで呼び出し
2.serverはcontroller.index関数へルーティング
3.controller.index関数でMongoDBにアクセス
4.MongoDBがデータを返す
という流れになる。
 

3.1. APIを追加する

Ctrl + Cでgruntを止めて、下記のコマンドを実行する。

yo angular-fullstack:endpoint member

下記のダイアログは全てデフォルトにする。
[?] What will the url of your endponit to be? /api/members
すると下記のファイルが作成される。
create server/api/member/index.js
create server/api/member/member.controller.js
create server/api/member/member.model.js
create server/api/member/member.socket.js
create server/api/member/member.spec.js
 

3.2. スキーマを定義する

メンバー一覧に表示する名前を定義する。
server/api/member/member.model.jsを下記の通り編集する。

編集前
'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));

var MemberSchema = new mongoose.Schema({
  name: String,
  info: String,
  active: Boolean
});

export default mongoose.model('Member', MemberSchema);

 

編集後
'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));

var MemberSchema = new mongoose.Schema({
  name: String,
});

export default mongoose.model('Member', MemberSchema);

 

3.3. MongoDBにデータを追加する

MongoDBに初期データを追加する。
server/api/member/member.controller.jsのMember変数定義以降に下記を追加する。

編集前
'use strict';

import _ from 'lodash';
import Member from './member.model';

 

編集後
'use strict';

import _ from 'lodash';
import Member from './member.model';

Member.find({}).remove(function() {
  Member.create({
    name: '田中_2'
  }, {
    name: '鈴木_2'
  }, function(err) {
    console.log('finished populating Members');
  });
});

 

3.4. サーバーにGETが定義されていることを確認する

server/api/member/index.jsに下記の通りgetが定義されていることを確認する。

router.get('/', controller.index);

 
server/api/member/member.controller.jsに下記の通りindex関数が定義されていることを確認する。

// Gets a list of Members
export function index(req, res) {
  Member.findAsync()
    .then(respondWithResult(res))
    .catch(handleError(res));
}

※上記のコードはangular-fullstackによって自動生成される。
 

3.5. APIに接続する

clientからserverのapiを参照する。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.members = [{name:'田中'},{name:'鈴木'}];
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });
  });

 

3.6. 動作確認する

http://localhost:9000/memberにアクセスして、下図の通り表示されれば成功である。
yeoman.01.04
 

4. clientからserverへPOSTする

処理の流れとしては、
1.clientからserverの/api/membersをPOSTで呼び出し
2.serverはcontroller.create関数へルーティング
3.controller.create関数でMongoDBにアクセス
4.MongoDBへデータを保存する
という流れになる。
 

4.1. メンバー登録フォームを追加する

メンバーページからメンバーを登録できるようにする。
client/app/member/member.htmlを下記の通り編集する。

編集前
<h2>メンバー一覧</h2>
<ul>
  <li ng:repeat="member in members">
    {{member.name}}
  </li>
</ul>

 

編集後
<div ng-controller="MemberCtrl">
  <h2>メンバー登録</h2>
  <form >
    <legend>メンバー登録</legend>
    <label>名前</label> 
    <input type="text" id="name" name="name" ng-model="member.name"> 
    <button class="btn btn-primary" ng-click="createMember()">登録</button>
  </form>

  <h2>メンバー一覧</h2>
  <ul>
    <li ng:repeat="member in members">
      {{member.name}}
    </li>
  </ul>
</div>

 

4.2. 登録ボタンのイベントハンドラーを追加する

ボタンが動作するように、イベントハンドラーを追加する。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $scope.members.push({
          name: $scope.member.name
        });
        $scope.member.name = '';
      }
    };
  });

 

4.3. 動作確認する

http://localhost:9000/memberにアクセスして、任意の名前(例では佐藤)を入力して登録ボタンを押して、下図の通り表示されれば成功である。
yeoman.01.05
 

4.4. 登録データをPOSTする

このままではサーバーにデータが保存されていないので、データをサーバーへPOSTする。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $scope.members.push({
          name: $scope.member.name
        });
        $scope.member.name = '';
      }
    };
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.members.push({
            name: $scope.member.name
          });
          $scope.member.name = '';
        });
      }
    };
  });

 

4.5. サーバーにPOSTが定義されていることを確認する

server/api/member/index.jsに下記の通りpostが定義されていることを確認する。

router.post('/', controller.create);

 
server/api/member/member.controller.jsに下記の通りcreate関数が定義されていることを確認する。

// Creates a new Member in the DB
export function create(req, res) {
  Member.createAsync(req.body)
    .then(respondWithResult(res, 201))
    .catch(handleError(res));
}

※上記のコードはangular-fullstackによって自動生成される。
 

4.6. 動作確認する

http://localhost:9000/memberにアクセスして、任意の名前(例では佐藤)を入力して登録ボタンを押して、さらにページ更新して、下図の通り表示されれば成功である。
yeoman.01.05
 

5. socket.ioでページを同期させる

angular-fullstackではsocket.ioをページの同期に使っているようなので、それについても説明する。
処理の流れとしては、
1.clientでページ読み込み時(/api/memberをGETする時)にsocket通知によるデータ配列追加/削除処理を登録しておく。(syncUpdates関数)
2.serverではMongoDBにデータを追加した際にMongoDBのポストホック機能(Member.schema.post関数)からホックしている全てのclientへsocket通知を送信する。
3.clientではsocket通知を受け取ったら、データ配列に追加する。(データは$scope内に定義しているので、angularJSにより自動的にページ更新がかかる。)
という流れになる。
 

5.1. socket.syncUpdates()を呼び出す

client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.members.push({
            name: $scope.member.name
          });
          $scope.member.name = '';
        });
      }
    };
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http, socket) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
      socket.syncUpdates('member', $scope.members);
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.member.name = '';
        });
      }
    };
  });

変更点は、
・/api/membersのGET時にsocket.syncUpdates呼び出し
・socket.syncUpdatesでメンバー一覧の更新が行われるので、createMember関数でのメンバー追加処理は削除する
 

5.2. socket.syncUpdates()の定義を確認する

/client/components/socket/socket.service.jsにsyncUpdates関数が定義されていることを確認する。

      /**
       * Register listeners to sync an array with updates on a model
       *
       * Takes the array we want to sync, the model name that socket updates are sent from,
       * and an optional callback function after new items are updated.
       *
       * @param {String} modelName
       * @param {Array} array
       * @param {Function} cb
       */
      syncUpdates: function (modelName, array, cb) {
        cb = cb || angular.noop;

        /**
         * Syncs item creation/updates on 'model:save'
         */
        socket.on(modelName + ':save', function (item) {
          var oldItem = _.find(array, {_id: item._id});
          var index = array.indexOf(oldItem);
          var event = 'created';

          // replace oldItem if it exists
          // otherwise just add item to the collection
          if (oldItem) {
            array.splice(index, 1, item);
            event = 'updated';
          } else {
            array.push(item);
          }

          cb(event, item, array);
        });

        /**
         * Syncs removed items on 'model:remove'
         */
        socket.on(modelName + ':remove', function (item) {
          var event = 'deleted';
          _.remove(array, {_id: item._id});
          cb(event, item, array);
        });
      },

※上記のコードはangular-fullstackによって自動生成される。
 

5.3. MongoDBのポストフック機能を使ってsocket通知が呼び出されていることを確認する

server/api/member/member.socket.jsにMongoDBへのデータ追加時にsocketからmember:saveが通知されることを確認する。

/**
 * Broadcast updates to client when the model changes
 */

'use strict';

var MemberEvents = require('./member.events');

// Model events to emit
var events = ['save', 'remove'];

export function register(socket) {
  // Bind model events to socket events
  for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
    var event = events[i];
    var listener = createListener('member:' + event, socket);

    MemberEvents.on(event, listener);
    socket.on('disconnect', removeListener(event, listener));
  }
}


function createListener(event, socket) {
  return function(doc) {
    socket.emit(event, doc);
  };
}

function removeListener(event, listener) {
  return function() {
    MemberEvents.removeListener(event, listener);
  };
}

※上記のコードはangular-fullstackによって自動生成される。
 

5.4. socket通知の登録処理が呼び出されていることを確認する

server/config/socketio.jsでregistar関数が呼ばれていること確認する。

// When the user connects.. perform this
function onConnect(socket) {
  // When the client emits 'info', this listens and executes
  socket.on('info', data => {
    socket.log(JSON.stringify(data, null, 2));
  });

  // Insert sockets below
  require('../api/member/member.socket').register(socket);
  require('../api/thing/thing.socket').register(socket);

}

※上記のコードはangular-fullstackによって自動生成される。
 

5.5. 動作確認する

ブラウザを2ウィンドウ開き、http://localhost:9000/memberにアクセスして、一方に任意の名前(例では藤原)を入力して登録ボタンを押して、もう一方に登録内容が反映されれば成功である。
yeoman.01.06
 

6. デバッガ(node-inspector)を起動する

デバッガの起動方法を説明しておく。
下記のコマンドを実行する。

grunt serve:debug

するとブラウザが自動起動してnode-inspectorが開く。
‘–debug-brk’オプション付きで起動されるので、必ずサーバー側のソースコードの先頭でブレイクする。

7. gruntでブラウザ指定する

起動するブラウザを変えたい場合、Gruntfile.jsを下記の通り変更する。

通常起動のブラウザを指定する方法(例ではchromium-browserを指定している)

  grunt.initConfig({
  //省略...
    open: {
      server: {
        url: '<%= express.options.domain %>:<%= express.options.port %>',
        app: 'chromium-browser'
      }
    },

デバッガのブラウザを指定する方法(例ではchromium-browserを指定している)

    nodemon: {
    //省略...
      debug: {
      //省略...
        options: {
       //省略...
          callback: function (nodemon) {
            // opens browser on initial server start
            nodemon.on('config:update', function () {
              setTimeout(function () {
                require('open')('http://localhost:8080/debug?port=5858', 'chromium-browser');
              }, 500);
            });
          }
        }
      }
    },

Comment ( 1 )

  1. SimtterCom » herokuのMEANからCloudinaryに画像をアップロードしよう

    […] takでMEAN(MongoDB+Express+AngularJS+Node.js)アプリを作成済み  この方法はYEOMANを使ってMEAN(MongoDB+Express+AngularJS+Node.js)を作成しようを参照すること 2.アプリをherokuにデプロイ済み  この方法はangula […]

Leave a reply

Your email address will not be published.

Time limit is exhausted. Please reload CAPTCHA.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>