読者です 読者をやめる 読者になる 読者になる

プログラミング 美徳の不幸

Ruby, Rails, JavaScriptなどのプログラミングまとめ、解説、備忘録。

初めてのNode.js (node.js+express.js+mongodb)※MacOSX

JavaScriptアツいよ!とよく言われます。まぁ確かに僕はJavaScriptからプログラミングを初めたのでJavaScriptの可能性も難しさもある程度知っています。

まず第一に、JavaScriptRubyだと高度な話以前にメソッドの分かりやすさが違う。
Rubyなら
"12345".to_i
と書けるのもJavaScriptだと
parseInt("12345");
となります。
細かい差かもしれません。ちなみになぜ上のほうがわかりやすく感じるかは、オブジェクト指向云々以前に日本人の言語発想として
◯◯を◯◯に、
というのがあるので、to_iとかto_sとか、そういうメソッドになったのかもしれませんね。

第二に、よく言われていることですがJavaScriptの複雑さと設計の悪さ。
関数が第一級オブジェクトということで、関数を渡してコールバック処理するなどがJavaScriptの花形だと思うのですが、その結果大量の()と{}および;(セミコロン)が必要な部分と要らない部分がこんがらがってくる。
こういう書式的な面と、もう一方がグローバル変数の問題。ここらへんはJavaScript GoodPartsなどに詳しいので興味があればそちらを参照してください。
そして、言語自体がPythonRubyのようなオブジェクト指向とは異なるパラダイムオブジェクト指向であること。つまりprototype継承の概念が初学者には難しい点。

しかし、これだけ悪いパーツ、理由がありながらJavaScriptがこれからアツイというのはどういうことなのか?

1点目がNode.jsの存在。これは後述。
2点目がクライアントサイド(ブラウザ)で使用出来る事実上唯一の言語であること。そしてこのUI側の開発でも、例えばモバイルとPCのデザインの差異を吸収するとか、クライアント側でもMVCパターンを使用した開発手法が出来上がってきている点。ここらへんがアツイ要因なのでしょう。

で、今までWebアプリに関してはRailsがあったのであまりNodeには興味がなかったのですが、そういうレガシーな発想はやめようと思ってちょっとだけサンプルのものを作ってみました。といっても、初歩の初歩、Node.jsでRails風のCRUDの流れを再現するまでですが。

導入準備

◯インストール

※nvm(node version manager, rvmのようなもの)のインストール
mkdir ~/.nvm
git clone git://github.com/creationix/nvm.git ~/.nvm
source ~/.nvm/nvm.sh
nvm install v0.8.4
nvm use v0.8.4
node --version #=>v0.8.4

◯mongoのインストールと起動(フォアグラウンドで)

brew install mongodb
mkdir ~/Library/MongoDB_Data
mongod --dbpath=/Users/inouetomoyuki/Library/MongoDB_Data

◯PATHの追加

※nvmでインストールしたnodeのbinのパスを通すために.zshrcの編集
export PATH=$HOME/.nvm/v0.8.4/bin/:$PATH

◯expressによる新規アプリ作成

npm install -g express
express firstapp
cd firstapp
npm install -d
npm install mongoose

npm installの-gオプションはGlobal。したがってパスが通っていればどこでも使えるようになります。一方でこれを使えない場合はライブラリは/node_modulesに入る。

だから重複が嫌ならGlobalオプションを使用して、アプリごとにインストールする際はシンボリックリンクを貼るようにすればいいんだろうけど、ここらへんのベストプラクティスはまだよくわからない。

アプリ本体の作成


今回作るのは単純なメモアプリ。内容としてはとても簡単で、title:stringとbody:stringを持つmemoというモデルを用意して、これに対するCRUD
だからRuby on Railsなら
rails g scaffold memo title:string body:string
のたった1コマンドだけで作れるもの(migrationとかあるけど)をnodeで作るだけの話です。

◯前提
以上の操作によって、ディレクトリ構成が以下のようになっている。
app_root

-app.js
-models
-node_modules
-package.json
-public
-routes
-views

※modelsだけは自分でmkdirしてください。

1.モデルの作成
ここは他より独立しているのでここから作ることにします。
◯models/memo.js

var mongo = require('mongoose');
mongo.connect('mongodb://localhost/firstapp');

var Schema = mongo.Schema;

var Memo = mongo.model('memos', new Schema({
  name: String,
  body: String,
  createAt:{type:date, default: Date.now}
  })
);
module.exports = Memo;

2.routingおよびcontrollerの作成
routesとは言っていますが、実際のroutesはapp.jsにおいてURLのマッチを行なっているのでどちらかというとcontrollerです。
◯routes/index.js

/*
  * GET home page.
  */
exports.index= function(req,res){
  res.render('index', {title:"Express"});
};

exports.memo = require('./memo');

一つのroutesの中に全て書くのは見通しが悪いので、一つのresourceに対して一つのroutesを作るようにする。
◯routes/memo/index.js

memo = require('../../models/memo');
exports.index = function(req, res){
  memo.find({}, function(err, memos){
    if(err) throw err;
    res.render('memos/index', {title: "Memo Book", memos:memos});
  });
};
exports.show = function(req, res){
  memo.findOne({_id:req.param('id')}, function(err,memo){
    if(err) throw err;
    res.render('memos/show', { title: 'Memo(show)', memo:memo });
  });
};

exports.edit = function(req, res){
  memo.findOne({_id:req.param('id')}, function(err, memo){
    if(err) throw err;
    res.render('memos/edit',{title:'Memo(Edit)',memo:memo});
  });
};

exports.new = function(req, res){
  res.render('memos/new',{title:'Memo(new)'});
};

exports.update = function(req,res){
  memo.findById(req.param('id'), function(err,memo){
    if(!memo)
      throw err;
    else{
      memo.name = req.param('name');
      memo.body = req.param('body');
      memo.save(function(err){
        if(err)
          throw err;
        else
          res.redirect('/memo/show/'+memo._id);
      });
    }
  });
};

exports.create = function(req,res){
  var me = new memo();
  me.name = req.param('name');
  me.body = req.param('body');
  me.save(function(err){
    if(err) throw err;
    res.redirect('/memos/index');
  });
};

exports.destroy = function(req,res){
  memo.remove({_id:req.param('id')}, function(err){
    if(err) throw err;
    res.redirect('/memos/index');
  });
};

3.app.jsの更新

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , memo = require('./models/memo')
  , path = require('path');

var app = express();

app.configure(function(){
  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')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.get('/', routes.index);
app.get('/memos/index',routes.memo.index);
app.get('/memo/show/:id',routes.memo.show);
app.get('/memo/edit/:id',routes.memo.edit);
app.get('/memos/new',routes.memo.new);
app.post('/memo/:id',routes.memo.update);
app.post('/memos',routes.memo.create);
app.get('/memo/delete/:id',routes.memo.destroy);

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

今日はとりあえずここまで。解説およびviewは次回。