herokuでNode.jsを使ってchatアプリ その3(MongoDBを利用して、メッセージを永続化)

さてさて、第三弾です。
f:id:yosuke_furukawa:20110805134625p:image

今回はMongoDBを利用して、メッセージの永続化にまで挑戦してみました。

Yosuke Chat test

MongoDB自体の勉強から入ったのですが、MongoDBはすごく簡単で良いですね。
特にNode.jsとの相性が良いです。ライブラリも豊富ですし、割とドキュメントも揃っています。

今回はherokuのaddonであるmongoHQを利用します。
まずはadd-onを実行するための下記のコマンド実行してください。

$ heroku addons:add mongohq:free

これであなたのherokuアプリケーションから利用できるようになります。
※add-onを有効にするためには、herokuのユーザー検証が必要になります。
ユーザー検証にはクレジットカード番号等も必要になりますが、100MBの範囲で利用する分には無料です。

Node.jsとmongoDBをつなぐためのライブラリとして、今回はmongooseを利用します。
単純にインストールするだけであれば、下記の手順でOKです。

$ npm install mongoose

もちろんmongodbを利用するなら、installが必要になりますが、下記のサイトを見れば出来ると思います。
Quickstart - MongoDB

herokuへのdeploy用にpackage.jsonを変更します。
package.json

{
  "name": "node-example",
  "version": "0.0.2",
  "dependencies": {
    "express": "2.2.0",
    "socket.io": "0.6.18",
    "ejs": "0.4.3",
    "mongoose": "1.7.4"
  }
}

mongooseの使い方は詳しくは下記のサイトを閲覧してください。
[mongodb] - Last Verse
まずは、スキーマを定義する必要があります。
スキーマの定義方法は下記の方法です。

var mongoose = require('mongoose');
//スキーマ定義
var Schema = mongoose.Schema;
var commentSchema = new Schema({
    body :String,
    date    :Date
});

//uriの設定、herokuで利用するときはprocess.env.MONGOHQ_URLを利用する。
//ローカルではmongodb://[hostname]/[dbname]形式で指定する。
var uri = process.env.MONGOHQ_URL || 'mongodb://localhost/mongo_data';

//mongodbへの接続
mongoose.connect(uri);
//スキーマの設定
mongoose.model('Comment', commentSchema);

この他にも属性に制約を入れたりできるらしいですが、まだ試していません。

最終的には下記のようになりました。
app.js

var express = require('express');
var app = express.createServer();
var ejs = require('ejs');
var io  = require('socket.io');
var mongoose = require('mongoose');

var port = process.env.PORT || 3000;
var Schema = mongoose.Schema;

var commentSchema = new Schema({
    body :String,
    date    :Date
});

var uri = process.env.MONGOHQ_URL || 'mongodb://localhost/mongo_data';
console.log( uri );

commentSchema.pre('init', function(next) {
    console.log('initialized');
    next();
});

commentSchema.pre('save', function(next) {
    console.log('pre save.');
    next();
});

app.configure(function() {
    var expressStatic = express.static(__dirname + '/static');
    app.use(expressStatic);
    //mongodb://[hostname]/[dbname]
    mongoose.connect(uri);
    mongoose.model('Comment', commentSchema);
});

var Comment = mongoose.model('Comment');

app.set('view engine', 'ejs');
app.set('view options', { layout: false });
app.set('transports', ['xhr-polling']);
app.set('polling duration', 10);
app.get('/', function(req, res) {
    console.log('/');
    res.render('index', { locals: { port: port } });
});

app.listen(port);

var socket = io.listen(app);
socket.on('connection', function(client) {
    Comment.find(function(err, docs) {
        if(!err) {
            for (var i = 0; i < docs.length; i++ ) {
                console.log(docs[i].body);
                console.log(docs[i].date);
                var message = JSON.stringify({date:docs[i].date, body:docs[i].body});
                client.send(message);
            }
        }
    });
    client.on('message', function(msg) {
        console.log('send :' + msg);
        var date = new Date();
        var sanitizedMsg = escapeHTML(msg);
        var message = JSON.stringify({date:date, body:sanitizedMsg});
        client.send(message);
        client.broadcast(message);
        var comment = new Comment();
        comment.body = sanitizedMsg;
        comment.date = date;
        comment.save(function(err) { 
            if ( !err ) console.log('save.');
        });
    });
    client.on('disconnect', function() {
         console.log('disconnect');
         // mongooseのdisconnectってどこでするのが適切?
    });
});

function escapeHTML(str) {
    return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

console.log('Server running at ' + port + '/');

mongooseのdisconnectをどこでするのが適切なのか
よくわかっていないので、もう少し調べているところです。

次にクライアント側にも一部変更がかかっていますが、そんなに変更はありません。
JSON形式でメッセージをやり取りするようにしただけです。
static/js/client.js

$(function() {
    var socket = new io.Socket();
    socket.connect();
    socket.on('connect', function() {
        console.log('connect');
    });
    socket.on('message', function(msg) {
        //JSON形式で送受信するようにしただけ。
        var obj = JSON.parse(msg);
        $('#list').prepend($('<dt>' + obj.date + '</dt><dd>' + obj.body + '</dd>'));
    });
    socket.on('disconnect', function(){
        console.log('disconnect');
    });

    $('#form').submit(function() {
        var message = $('#message');
        socket.send(message.val());
        message.attr('value', '');
        return false;
    });
});

いや〜、それにしてもmongodbはかなり簡単で良いですね。
mongoDBの勉強会とかあるなら教えてもらいたいくらいです。
割と使えそうなので、本業でも利用してみようかなぁと思うくらいです。