node.js + express + mongoose で TODOアプリを作ってみる

node.js + express + mongoose で TODOアプリを作ってみました。

Github: smeghead / node.js-sample-app

node.js

サーバサイドJavaScriptを試してみたかったのと、node.jsのシングルスレッドベースの非同期処理環境 上でのプログラミングが実際書き易いのかってのも試してみたかった。

MongoDB

ストレージとしてMongoDBを使うことで、データ表現もjsonで記述できてしまうというのが、良さそうだった。

node.js のライブラリ

結果、node.js 上のWebアプリフレームワークである express と、mongodbのアクセスライブラリである mongooseをインストールして、簡単なTODOアプリを作りはじめた。

準備

これらのインストール。試す場合はググって下さい。

  • node.js
  • npm
  • express
  • mongoose
  • mongodb

モデル(mongoose)

mongooseは、最近書き方が大きく変わったらしい。 Webで検索したところが古い書き方をしていたようで、ちょっと嵌ってしまった。

最終的には、本家を見ながら書きました。 https://github.com/LearnBoost/mongoose

// todo.js
require.paths.unshift('vendor/mongoose');
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost:27017/todo');
var Schema = mongoose.Schema;
 
var Todo = new Schema({
  name: {type: String, index: true},
  todo_state: Number,
  memo: String,
  created_at: Date
});
Todo.pre('save', function(next) {
  if (this.isNew) {
    this.todo_state = 0;
    this.created_at = new Date();
  }
  next();
});
mongoose.model('Todo', Todo);
 
//module.exports = Todo;
module.exports = db.model('Todo');
 
// vim: set ts=2 sw=2 sts=2 expandtab fenc=utf-8:

Schemaを定義して、そのスキーマに対して、カラムやら、トリガやらの内容を指定するようです。 Todo.pre で、save前に実行する内容を指定していますが、挿入時も更新時も呼ばれるので、新規追加のときだけ実行したい場合は、this.isNewによって切り分けが必要です。(これのせいで、更新できないとか、嵌りました)

Webアプリ(express)

expressをインストールすると、expressというコマンドがインストールされるので、 コマンド実行で、Webアプリの雛形ができあがります。

$ express -s -t ejs node.js-sample-app

コントローラなどなど

app.js というファイルを引数にnodeコマンドを実行することで、サーバを起動することになる。 今のところ、URLも単純なので、コントローラまわりなどは、app.jsにそのまま書いてます。

/**
 * Module dependencies.
 */
 
var express = require('express');
 
var app = module.exports = express.createServer();
var Todo = require('./models/todo.js');
 
// Configuration
 
app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser());
  app.use(express.session({ secret: 'your secret here' }));
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});
 
app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 
});
 
app.configure('production', function(){
  app.use(express.errorHandler()); 
});
 
// Routes
 
app.get('/', function(req, res){
  console.log('requested /.');
  Todo.find({}, function(err, todos) {
    console.log('finded.');
    res.render('index', {
      title: 'Express',
      todos: todos,
    });
  });
});
app.post('/register', function(req, res) {
  console.log(req.body.todo);
  var todo = new Todo(req.body.todo);
  todo.save(function(err){console.log(err);});
  res.redirect('/');
});
app.put('/checked/:id', function(req, res) {
  console.log('/checked');
  console.log(req.params.id);
  Todo.findById(req.params.id, function(err, todo) {
    todo.todo_state = req.body.todo_state;
    todo.save(function(err){console.log(err);});
    console.log('saved');
  });
  res.redirect('/');
});
 
// Only listen on $ node app.js
 
if (!module.parent) {
  app.listen(3000);
  console.log("Express server listening on port %d", app.address().port);
}
// vim: set ts=2 sw=2 sts=2 expandtab fenc=utf-8:

mongooseのモデルを使ってDBアクセスしてます。 DBアクセスのコードが、コールバックベースになります。 予想通りモデルTodoのfindメソッドが検索するメソッドなんですが、 第一引数に条件、第二引数に検索後の処理を関数で指定します。 検索でエラーがあると、errに内容が格納され、検索が正常終了すると、errはnullで、todosに 検索結果が格納されるという寸法のようです。 あと、viewの表示は、res.renderメソッドに、viewのファイル名(拡張子無し)とデータを渡す形です。

  Todo.find({}, function(err, todos) {
    console.log('finded.');
    res.render('index', {
      title: 'Express',
      todos: todos,
    });
  });

view

ejsは、自然なテンプレートエンジンです。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<h1>Todo</h1>
<form action="/register" method="post">
  <input type="text" name="todo[name]" value="" /><br/>
  <textarea name="todo[memo]"></textarea><br/>
  <input type="submit" value="add" />
</form>
 
<h1>TODO List</h1>
<dl>
<% for (var i = 0; i < todos.length; i++) { %>
  <dt>
    <form action="/checked/<%= todos[i]._id %>" method="post" class="check">
      <input type="hidden" name="_method" value="put">
      <input type="checkbox" class="todo_state" name="todo_state" value="1" <% if (todos[i].todo_state != 0) { %>checked="checked"<% } %>>
    </form>
    <%= todos[i].name %>
    <%= todos[i].todo_state %>
  </dt>
  <dd>
    <%= todos[i].memo %>
  </dd>
<% } %>
</dl>
<script type="text/javascript">
  $('form.check input.todo_state').change(function(){
    var checkbox = $(this);
    var form = checkbox.parent();
    checkbox.val(checkbox.attr('checked') ? 1 : 0);
    form.submit();
  });
</script>
<!-- vim: set ts=2 sw=2 sts=2 expandtab fenc=utf-8: -->

という感じで、node.jsで一応アプリを書けるベースの雰囲気を掴むことはできました。 まだ、入力内容のvalidationやXSS対策などの部分は見てないので、これから。 続く

2 Comments

  • At last! Something clear I can unrddstane. Thanks!

  • I have checked your website and i’ve found some duplicate content, that’s why you don’t rank high in google’s search results, but there is a tool that can help you to create 100% unique articles, search for; Boorfe’s tips unlimited content

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.