Node.js+jQuery Mobile+MongoDBでCRUDアプリケーションを作る(その1)

はじめに

この記事は、JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース) の 11 日目の記事です。

Node.jsを最初に見たときの印象は、「これで簡単なWebアプリケーションをサクサク作れそう!」というものでした。スレッドではなくイベントループで、リアルタイムなWebアプリケーションの開発を容易するという特徴があるのですが、何よりも、javascriptで簡単にプロトタイプできそうという点が気に入りました。
#もともとGateway屋さんなので、イベントループで、ステートマシン的なプログラムの方が親しみがあったという点もあるかもしれません。

そこで本記事では、Node.js+jQuery Mobile+MongoDBを使ってシンプルなCRUDアプリケーションを作ってみようと思います。作るアプリケーションはMemoアプリで、今回は「その1」として、Memoの新規登録(Create)、一覧表示(Read)までが範囲です。

事前準備

今回のCURDアプリケーションでは次の技術を使います。

  1. Node.js
  2. Express
  3. Jade
  4. jQuery Mobile
  5. Mongoose
  6. MongoDB

インストールは他に譲ることにします。

JadeでjQuery Mobileのページを作る

画面イメージはこちら。

画面はjQuery Mobileを使って作ります。Jadeを使うとシンプルに書くことができそうです。

views/layout.jade
!!! 5
html
  head
    title='Memo'
    meta(name='viewport', content='width=device-width, initial-scale=1')
    link(rel='stylesheet', href='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css')
    script(src='http://code.jquery.com/jquery-1.6.4.min.js')
    script(src='http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js')
  body!= body
  • 共通的な必要事項をlayout.jadeに記載し、各ページはindex.jadeに記載します。
  • HTML5で宣言します。
views/index.jade

Top Page(一覧表示画面)

div(data-role='page', id='index')
  div(data-role='header')
    h1 Memo

  div(data-role='content')
   ul#memolist(data-role='listview', data-inset='true')

  div(data-role='footer', data-position='inline')
    a(href='#add', data-icon='plus', class='ui-btn-left') Add

Add Page(新規登録画面)

div(data-role='page', id='add')
  div(data-role='header', data-position='inline')
    a(data-rel='back', data-icon='back') Back
    h1 Memo Add
    a#save-btn(href='./', data-icon='check', class='ui-btn-right', rel='external') Save

  div(data-role='content')
    div(data-role='filedcontain')
      label(for='memo-add') Memo
      textarea#memo-add

  div(data-role='footer', data-position='inline')
    a(href='#add', data-icon='plus', class='ui-btn-left') Add

script部

script
  $(function() {
    setTimeout(function(){
      $.get('memo', onGetMemoList);
    });

    function onGetMemoList(data) {
      for(var index=0; index<data.length; index++) {
        $("#memolist").append('<li><a href="#view" id="' + data[index]._id + '"><h3>' + data[index].content + '</h3></a></li>').listview('refresh');
      };
    };

    $("#save-btn").bind('click', function(e) {
      $.post(
        'memo',
        { content : $("#memo-add").val() },
        onSuccess,
        'json'
        );
    });

    function onSuccess(data) {
      alert(data.message);
    };
  });
  • RESTfulなAPIを呼び出し、画面側でjsonデータをバインドする方式とします。
  • 初期表示時を上手く処理する方法が思いつかなかったので、取りあえずsetTimeoutを使ってます。(ここはjade側でバインドすべきか、、、もっと良い方式がないものか)
  • $.postでdataTypeをjson指定することで、サーバ側でそのままjsonとして処理できます。

Node.js+MongoDBでサーバ側を作る

必要モジュールの宣言
require.paths = require.paths.unshift(__dirname + '/../node_modules');

var express = require('express')
  , mongoose = require('mongoose')
  , routes = require('./routes')
モデル定義とDBへの接続(mongoose)
var Schema = mongoose.Schema;
var Memo = new Schema({
  content : String,
  date : Date
});
Memo.pre('save', function(next) {
  this.date = new Date();
  next();
});
mongoose.model('memo', Memo);

var db = mongoose.createConnection('mongodb://localhost/memo');
var Memo = db.model('memo');
  • Memoモデルを保存する場合にdateパラメータを現在日時で自動設定するように定義してます。
  • mongooseでモデルと定義して、Memoモデルを保存する場合、dbname=memo, collections=memosとcollectionsの名称はsが自動で付きます。
RESTfulなAPI

HTTPのメソッドとURIは以下の方針とします。

  • app.get '/memo' MemoのRead(一覧取得)
  • app.post '/memo' MemoのCreate 
  • app.get '/memo/:id' MemoのRead
  • app.put '/memo/:id' MemoのUpdate
  • app.del '/memo/:id' MemoのDelete
一覧表示処理
app.get('/', function(req, res) {
  res.render('index');
});

app.get('/memo', function(req, res, next) {
  Memo.find({}, function(err, data) {
    if(err) return next(err);
    res.json(data);
  });
});
  • 検索条件を{}にすることで全件検索をしています。本来は上限指定をすべきですね。
新規登録処理
app.post('/memo', function(req, res, next) {
  var memo = new Memo();
  memo.content = req.body.content;
  memo.save(function(err) {
    if(err) return next(err);
    res.json({ message : 'Success!'});
  });
});
  • json指定でクライアント側から送信しているため、req.bodyは自動でjsonとして解釈されます。
  • saveした際に先にモデルの所で定義したpre処理が入り、memo.dateが現在日時に更新されます。

 (その2へ続く)

(追記)

ここまでの感想

 javascript一本で、クライアントからサーバまで書けるのは、気持ちがよいです。
 良い意味で、コンテキストスイッチが切り替わらないのが良いです(今までHTML, CSS, javascript, JSP, Java, SQLだったものが、HTML, CSS, javascriptだけになる)。

 Node.js, MongoDB(mongoose)はサクサク行くのに、jQuery Mobileがはまりますね。
 jQuery Mobileはマークアップでほとんど書けるという点が魅力的で、覚えれば決まったパターンで書けるのは確かなのですが、取り回しが難しい所も多々ありますね。

ご意見、アドバイスなどありましたら、ぜひ!

 Node.js, express/jade, jQuery Mobile, MongoDB/mongooseなど、ほとんど初めて使ってみた感満載ですので、ぜひ「もっとこうできる!」「こんな実現方式もある」などご意見頂ければ嬉しいです。
 というか、listviewで選択した行を取得する所で、ステキな実装案はないでしょうか。。。

(追記)bind, live, delegateの違いを理解すれば解決できました。orz