最近流行りの Node.js と MongoDB の組み合わせで開発環境を作ったメモ。

DLとインストール

Node.js

Node.js の本体。

  • https://nodejs.org/download/
    • Installer を選択
    • Binary は Node.exe だけで npm が無い

インストールはインストーラに従えばOK。

Eclipse プラグイン

nodeclips という プラグインをインストールする。
express というフレームワークも一緒に入る。

  • http://dl.bintray.com/nodeclipse/nodeclipse/
    • とりあえず配下全部

プラグイン、インストール後に環境設定が必要。

注:node.exe以外はデフォルトの設定を使うこと。

MongoDB

MongoDB 本体はDLしてインストールするだけ。 起動方法法は後述。

  • https://www.mongodb.org/downloads
    • OS は 2008 R2+ にした

MongoDB の教本(通称:薄い本)のPDFをDLして一読しておく。

プロジェクトの作成

素の Node.js プロジェクトと Expless フレームワークのプロジェクトが作れるが今回は Express プロジェクトを作成する。
Express は Node.js を HTTP サーバとするためのフレームワーク。

  • メニューから 「新規 > プロジェクト > その他 > ノード > Node Express Project」 を選択。
  • テンプレートエンジンは好きなほうを選択
    • jade はHTML省略記法、ejs はJSPライクな感じ

作成されたプロジェクトの構成は以下

  • public/** : 静的なHTMLファイル
  • routes/** : JSのロジック
  • views/** : テンプレート
  • app.js : アプリのメイン
  • package.json : アプリの定義ファイル

app.js のメニューから 「実行 > Node Application」 でアプリが起動する。
ブラウザから http://localhost:3000 にアクセスして Welcom ページが表示されればOK。

Node.js と MongoDB の接続

MongoDB サーバの起動

mongod.exe を実行するだけでサーバは起動するが WindowsだとDBフォルダのドライブ指定を明示したほうが良いので起動バッチを作成する。 MongoDBのインストールフォルダに置いておく。

boot-mongod.bat: サーバ起動用

set BASEDIR=%~dp0
cd /d %BASEDIR%
%BASEDIR%\Server\3.1\bin\mongod.exe --dbpath %BASEDIR%\DB

clinet-mongo.bat: シェル起動用

set BASEDIR=%~dp0
cd /d %BASEDIR%
%BASEDIR%\Server\3.1\bin\mongo.exe

クライアントのシェルを起動して繋がればOK。

ドライバの準備

ドライバは Node.js のモジュール 'mongodb' を使用する。
'mongoose' とかも有るようだけどとりあえず標準を使う。

package.json の dependencies に "mongodb" を追加する。

{
  "name": "node-ex-test",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.2.6",
    "jade": "*",
    "mongodb": "*"     <--これ追加
  }
}

変更後にメニューから「実行 > npm install」を行うとmongodbのドライバがDLされる。
他のモジュールも同じように追加できる。

ドライバのAPIは以下を参照。

接続サンプルコード

MongoDBにコレクション accounts を作成しユーザ登録と認証を行う簡単なサンプル。
POSTのパラメータに user,pass が設定されるAPIとしている。

簡単な仕様:

メソッドURLリクエスト本文レスポンス
登録POST/accounts/registeruser={ユーザ名}&pass={パスワード}200 or 500
認証POST/accounts/loginuser={ユーザ名}&pass={パスワード}200 or 401

routes/accounts.js:

var MASTER_PASSWD = "ce39325bc505e74089f7e176a380370f";
var mongodb = require('mongodb');
var crypto = require('crypto');
var accounts;

mongodb.MongoClient.connect("mongodb://localhost:27017/test", function(err, database) {
    if (err != null) console.error(err);
    accounts = database.collection("accounts");
});

function pass2hash(pass, solt) {
    var hash = crypto.createHash('sha1');
    hash.update(MASTER_PASSWD).update(pass).update(solt);
    return hash.digest('base64');
}

exports.register = function register(req, res) {
    var solt = crypto.createHash('sha1').update(""+ new Date()).digest('base64');
    var hash = pass2hash( req.body.pass, solt);
    accounts.update({name: req.body.name},
        {name: req.body.name, hash: hash, solt:solt},
        {upsert: true},
        function(err, result) {
            if (err == null && result.result.n == 1) {
                console.log(item);
                res.send("OK");
            } else {
                console.error(err);
                res.statusCode = 500;
                res.send("NG");
            }
        }
    );
}

exports.login = function login(req, res) {
    accounts.findOne({name:{$eq: req.body.name}}, function(err,doc){
        if (err != null) console.error(err);
        var isLoginOk = false;
        if (doc != null) {
            var hash = pass2hash(req.body.pass, doc.solt);
            isLoginOk = (hash == doc.hash);
        }
        if (isLoginOk) {
            res.send("OK");
        } else {
            res.statusCode = 401;
            res.send("NG");
        }
    });
}

apps.js:

var express = require('express');
var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
    app.use(express.errorHandler());
}

// サンプルのマッピング設定
var accounts = require('./routes/accounts');
app.post("/accounts/register", accounts.register);
app.post("/accounts/login", accounts.login);

http.createServer(app).listen(app.get('port'), function() {
    console.log('Express server listening on port ' + app.get('port'));
});

実行結果

ユーザ登録を行ってMongoDBのシェルからDBにデータが登録された事を確認する。

> db.accounts.find();
{ "_id" : ObjectId("55cd9b6e8ca01a0a50ecd707"), "name" : "abc", "hash" : "+1j0H0OdNH/HFGXspXi+hx6mtS8=", "solt" : "RzB+u5FvnOvHRXvtZi85iX9URno=" }
{ "_id" : ObjectId("55d011f4ba68640523bd0271"), "name" : "xxx", "hash" : "wsB0iPj/2BtLHGXWmSFqeGjqwn4=", "solt" : "1o2QK3qqdWSgw1cFQgbIj4DnpBA=" }
{ "_id" : ObjectId("55d0126cba68640523bd0272"), "name" : "yyyy", "hash" : "lHj8Xu6HNrrpjn9w6i9FnWYUwdI=", "solt" : "BI8xyULlX/CAj19/3VonYxehG/o=" }
{ "_id" : ObjectId("55d012bbba68640523bd0273"), "name" : "zzz", "hash" : "xeJTRfFbVaKSI2m6QePMv9laB4E=", "solt" : "NG42owW9EC7Ntu8FAp6NWXLwbBs=" }

つでに name に Index を作成しておく。

> db.accounts.ensureIndex({name:1},{unique:true});
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> db.accounts.getIndexes()
[
        {
                "v" : 1,
                "key" : { "_id" : 1 },
                "name" : "_id_",
                "ns" : "test.accounts"
        },
        {
                "v" : 1,
                "unique" : true,
                "key" : { "name" : 1 },
                "name" : "name_1",
                "ns" : "test.accounts"
        }
]

MongoDBはスキーマレスなのでこういう設定をするタイミングはよくわからない。
_id の役割を完全に理解してないが name の代わりに PKEY として使っても良いのかもしれない。

まとめ

Node.js と MongoDB どちらも JavaScript に慣れている人なら違和感なく入れると思う。
思いの他、学習コストは低いのではないだろうか。
Eclipse でデバッグ実行までできるので開発もしやすそう。

MongoDB の「トランザクションが無い」とか「正規化しない」などの特徴は慣れるまでは難しそうだ。