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

Node.js, jQuery Mobile, MongoDBを使ったRESTfulなCRUDアプリケーションが一通りできあがったので、まとめを書こうと思います。ユーザ認証やバリデーション、ページングなど実際に必要な処理は全くありませんが、とりあえずCRUDの骨組みとしては完成です。

これまでのエントリーは以下です(最新はここから少し変更を加えています)。

アプリケーションはnode-ninja上のこちらで動かしています。

また、このアプリケーションのソースは以下で公開しています。

画面イメージ




RESTfulなAPIURI

'memo'に対するgetを初期表示にしたため、一覧の取得APIを'memo/list'のgetにしました。

  • app.get 'memo/list' MemoのRead(一覧取得)
  • app.get 'memo/:id' MemoのRead(id指定)
  • app.post 'memo' MemoのCreate
  • app.put 'memo/:id' MemoのUpdate
  • app.del 'memo/:id' MemoのDelete

views/layout.jade

!!! 5
html
  head
    title='Memo'
    meta(name='viewport', content='width=device-width, initial-scale=1')
    meta(name='apple-mobile-web-app-capable', content='yes')
    meta(name='apple-mobile-web-app-status-bar-style', content='black')
    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
  • iphone用のフルスクリーンモードとステータスバーの変更用にmetaタグを追加
    • meta(name='apple-mobile-web-app-capable', content='yes')
    • meta(name='apple-mobile-web-app-status-bar-style', content='black')
    • ページをホーム画面に保存してから起動することで有効になります。

views/index.jade(viewページ抜粋)

div#view(data-role='page')
  div(data-role='header')
    a(data-rel='back', data-icon='back', data-derection='reverse') Back
    h1 Memo View
    a(href='#add', data-icon='plus') Add

  div(data-role='content')
    div(data-role='filedcontain')
      label(for='memo-view') Memo
      p#memo-view

  div.ui-bar(data-role='footer', data-position='fixed')
    div(data-role='controlgroup', data-type='horizontal')
      a(href='#edit', data-icon='gear', data-transition='flip') Edit
      a(href='#confirm-dialog', data-icon='delete', data-rel='dialog') Delete
  • footerを下部に固定するように変更
    • data-position='fixed'を指定
    • class ui-headerを指定してボタンの配置を制御していましたが、fixedが有効にならないので変更
    • headerについては、fixedを指定しない方が画面遷移時に安定します。
  • footerのボタンは水平方向にグループ化
  • Deleteボタン押下時にはConfirmダイアログを表示するように変更

views/index.jade(viewページ表示前処理抜粋)

    $("#view").bind('pagebeforeshow', function(e, ui) {
      $("#memo-view").html(' ');
      $.get(
        'memo/' + mstore.selectedid
        , function(data) {
            $("#memo-view").html(data.content);
          }
        );
    });
  • イベントをpageshowからpagebeforeshowに変更

app.js(RESTful API部抜粋)

app.get('/memo', function(req, res) {
  console.log("index");
  Memo.find({}, function(err, data) {
    if(err) return next(err);
    res.render('index', { memos: data });
  });
});

app.get('/memo/list', function(req, res, next) {
  console.log("get memos");
  Memo.find({}, function(err, data) {
    if(err) return next(err);
    res.json(data);
  });
});

app.get('/memo/:id', function(req, res, next) {
  console.log("get memo : " + req.params.id);
  Memo.findById({ _id : ObjectId(req.params.id)}, function(err, data) {
    if(err) return next(err);
    res.json(data);
  });
});

app.post('/memo', function(req, res, next) {
  console.log("post memo : " + req.body.content);
  var memo = new Memo();
  memo.content = req.body.content;
  memo.save(function(err) {
    if(err) return next(err);
    res.json({ message : 'Success!'});
  });
});

app.put('/memo/:id', function(req, res, next) {
  console.log("put memo : " + req.params.id);
  Memo.update(
    { _id : ObjectId(req.params.id) }
    , { content : req.body.content, date : new Date() }
    , { upsert : false, multi : false }
    , function(err) {
      if(err) return next(err);
      res.json({ message : 'Success!'});
  });
});

app.del('/memo/:id', function(req, res, next) {
  console.log("delete memo : " + req.params.id);
  Memo.findById({ _id : ObjectId(req.params.id)}, function(err, data) {
    if(err) return next(err);
    data.remove(function(err) {
      console.log("memo remove!");
      res.json({ message : 'Success!'});
    });
  });
});
  • 'memo'に対するgetは、取得したデータをindex.jadeにレンダリングして初期ページとして返します。
  • updateのupsertは対象がない場合にinsertするかどうかのオプション指定
  • updateのmultiは条件にマッチするデータを複数更新するかどうかのオプション指定

最後に

Node.js, MongoDB, jQuery Mobileを使ってRESTfulなCRUDアプリを一通り作ってみて、いろいろ学ぶことができました。やはり手を動かして作ってみるのが一番理解が進みますね。

一番ハマったのはjQuery Mobile。少しの条件の違いで挙動が変わってくるので、検証してパターンを作っていくことが必要だと感じました。まだまだ動きもモッサリしていますしね。凝ったエフェクトではなく、とことんスピードに拘ったモバイル系のフロントフレームワークが出てきたりすると面白いかもしれませんね。

Node.js+MongoDBは相性がよく、シンプルに書けるのがよいです(まぁ、たいした処理は組んでいませんが)。