基礎から学ぶNode.js
第4回 Node.jsアプリケーションとMongoDBの連携
はじめに
前回から,いよいよNode.jsを使ったWebアプリケーションを作成しはじめました。前回はデータを保存および読み出す処理がなかったので,今回はデータベースとの連携を実装したいと思います。使うのはMongoDBです。リレーショナルデータベースに対して,MongoDBはドキュメント指向データベースという位置づけです。JSON構造をそのまま保存できるため,Node.jsとの親和性も高いです。簡単に導入でき,日本語ドキュメントもあるので,初めてでもすんなり使えると思います。
MongoDBのセットアップ
MongoDBのサイトから,OSごとに用意されたアーカイブをダウンロードして展開してください。展開したフォルダの直下にあるbinディレクトリ内にコマンド群があるため,これらを使って操作します。サーバデーモンとなるのがmongodで,クライアントとなるのがmongoになります。
まず適当にデータファイルを置く空ディレクトリを用意してください。そこを指定してmongodを実行するだけサーバが起動します。
mongod --nojournal --noprealloc --dbpath <データディレクトリパス>
なお,MongoDBは基本的に最初から巨大なファイルを用意することで,パフォーマンスを上げる方針で作られているため,デフォルトで起動すると,何もデータを入れてなくてもGB単位でディスク領域を占有します。今回は単に開発で使用するので,ジャーナル機構やデータファイルを先に確保する機構は,無効化しています。フォアグランドで起動するので,CTRL + Cで終了できます。
Mongooseを使ってNode.jsからMongoDBを利用
MongoDBをnodeから利用するには,node-mongodb-nativeという標準ドライバモジュールがあります。今回はそれをさらにO/Rマッパーのように利用できるモジュールMongooseを使って,MongoDBを使ってみます。
前回作成したfirstappのディレクトリで関連モジュールをインストールします。
cd firstapp npm install mongoose
として利用するモジュールをインストールします。
モデルの実装
続いてmodel.jsというファイル名で,下記のコードを作成定義します。
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/firstapp');
function validator(v) {
return v.length > 0;
}
var Post = new mongoose.Schema({
text : { type: String, validate: [validator, "Empty Error"] }
, created: { type: Date, default: Date.now }
});
exports.Post = db.model('Post', Post);
まず,mongooseモジュールの読み込みです。次にconnectメソッドで,localhostで稼働するMongoDBのfirstappデータベースに接続します。
投稿データをPostというモデルで定義します。Postモデルのスキーマは,textという文字列フィールドと,dateという日付型でデフォルトは現在日時が入るフィールドを定義します。またtextフィールドには,「文字列長が0より大きいか」バリデート関数を指定しています。
modelメソッドは,「Post」という名前でPostモデルのスキーマを登録しています。MongoDB上では,「posts」というコレクション(RDBでいうテーブルに該当)で管理されます。Mongooseによって,小文字かつ複数形に変換された名前となります。戻り値がデータアクセスモデルとなるので,それをexportsに渡して,外から使えるようにします。
アプリケーションの修正
前回作成したroutes/main.jsを修正して,Postモデルを利用するようにします。
var model = require('../model');
var Post = model.Post;
exports.index = function(req, res){
Post.find({}, function(err, items){
res.render('index', { title: 'Entry List', items: items })
});
};
exports.form = function(req, res){
res.render('form', { title: 'New Entry' })
};
exports.create = function(req, res){
var newPost = new Post(req.body);
newPost.save(function(err){
if (err) {
console.log(err);
res.redirect('back');
} else {
res.redirect('/');
}
});
};
はじめに,model.jsを読み込みます。さらにPostモデルを直接利用できるように変数宣言しなおします。
ルートパスにアクセスしたときのリスト表示では,findメソッドを使って,postsコレクションのドキュメントを全て取得しコールバック関数function(err, items)で,結果を処理します。Findメソッドの第1引数は条件(SQLでいうwhere句)を指定できます。この場合全てなので空のオブジェクトにしています。コールバックの方は,第1引数にクエリ失敗時のerrオブジェクト,第2引数に正常時の結果が入ります。よって,errオブジェクトの有無で失敗と成功の切り分けをします。
/createに対する処理は,まずリクエストbodyの中身をそのままPostモデルのコンストラクタに渡して,一致するフィールド(ここではtext)の値をセットしつつ生成しています。そしてsaveメソッドを呼んでコレクションに書き込んでいます。コールバック関数function(err)で,成功か失敗かを判断し,エラー時は前の画面に戻しています。前の画面のURLは,Referrerヘッダを利用してExpressが自動で処理しています。設定した文字列長確認のバリデートに不適合だった場合もここでエラーとなります。
動作確認
以上で実装は完了です。アプリケーションを起動して,
node app.js
ブラウザから「http://localhost:3000/」にアクセスしてみましょう。
「New Entry」リンクからフォームにテキストを入力してPostしていくと,どんどんリストの項目が増えていくことがわかると思います。テキストを空でPostすると,コンソールに出力されるログでバリデートエラーが確認できます。
最後にMongoDBのシェルから実際のデータを確認してみます。mongodコマンドと同じ場所にあるmongoコマンド別のプロンプトから実行してください。まずshow dbsと命令すると,存在するデータベースの一覧が出ます。「use <データベース名>」で操作するデータベースを指定します。さらに「show collections」で,データベース内のコレクションの一覧が出ます。「db.<コレクション名>.find()」でコレクションの中身を表示します。
$ mongo
MongoDB shell version: 2.0.6
connecting to: test
> show dbs
firstapp 0.078125GB
local (empty)
> use firstapp
switched to db firstapp
> show collections
posts
system.indexes
> db.posts.find()
{ "text" : "最初の投稿です。", "_id" : ObjectId("4ff96c7d7f12ede516000001"), "created" : ISODate("2012-07-08T11:18:21.466Z") }
{ "text" : "2番目の投稿です。", "_id" : ObjectId("4ff96c8b7f12ede516000003"), "created" : ISODate("2012-07-08T11:18:35.904Z") }
{ "text" : "3番目の投稿です。", "_id" : ObjectId("4ff96ca37f12ede516000006"), "created" : ISODate("2012-07-08T11:18:59.385Z") }
_idというのは,MongoDBのドキュメントにおける主キーのようなもので,未指定だと自動で割り当てられます。また特にセットしていない作成日時createdもスキーマに定義したとおり,追加時点での現在日時が入っています。
最後に
今回は,Mongooseを使ってMongoDBに,前回のアプリケーションを接続してみました。シンプルではありますが,非常に少ないコードで実装が終わってしまったと思います。この手軽さがNode.jsの魅力の1つだと筆者は思っています。次回以降は,様々なクラウドサービス上でこのアプリケーションをデプロイして動かしていきたいと思います。

