用 Express 和 MongoDB 寫一個 todo list(node版本:4.1.1)

練習一種語言或是 framework 最快的入門方式就是寫一個 todo list 了. 他包含了基本的 C.R.U.D. ( 新增, 讀取, 更新, 刪除 ). 這篇文章將用 Node.js 裡最通用的 framework Express 架構 application 和 MongoDB 來儲存資料.

原始檔

https://github.com/y2468101216/node-wiki-gitbook/tree/master/src/todo_list

功能

  • 使用 facebook 登入, 用 session 來辨別每一問使用者
  • 可以新增, 讀取, 更新, 刪除待辦事項( todo item )

開發環境

開發環境 開始之前請確定你已經安裝了 Node.js, Express 和 MongoDB, 如果沒有可以參考本電子書.

Node.js 套件

本次我們將使用 Node.js, Express, MongoDB, express-generator, ejs 來開發

步驟

用 Express 的 command line 工具幫我們生成一個 project 雛形 預設的 template engine 是 jade, 在這裡我們改用比較平易近人的 ejs.


$ express todo_list -e --git

(只適用 Mac 使用者)在專案根目錄修改 .gitignore,在最後一行加入


#mac style file
.DS_Store

Welcome to Express

開啟 express server 然後打開瀏覽器瀏覽 127.0.0.1:3000 就會看到歡迎頁面.


$ DEBUG=todo_list npm start

Project 檔案結構


todo_list
|-- bin
|   |-- www
|  
|-- node_modules
|   |-- body-parser //負責處理post傳回來的資料
|   |-- cookie-parser //處理cookie
|   |-- debug
|   |-- ejs //view engine
|   |-- express //web server
|   |-- mongodb //db server
|   |-- morgan //紀錄http request log
|   `-- serve-favicon //db server 
|
|-- public
|   |-- images
|   |-- javascripts
|   `-- stylesheets
|       |-- style.css
|
|-- routes
|   `-- index.js
|
|-- views
|   |-- index.ejs
|   `-- error.ejs
|
|-- .gitignore
|
|-- app.js
|
`-- package.json
  • bin - 邏輯檔
  • node_modules - 包含所有 project 相關套件.
  • public - 包含所有靜態檔案.
  • routes - 路由檔.
  • views - 包含 action views, partials 還有 layouts.
  • app.js - 包含設定, middlewares, 和 routes 的分配.
  • package.json - 相關套件的設定檔.

安裝 mongoDB

打開 package.json,在 dependencies 插入兩行


 {
  "name": "todo_list",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.13.2",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "ejs": "~2.3.3",
    "express": "~4.13.1",
    "morgan": "~1.6.1",
    "serve-favicon": "~2.3.0",
    "mongodb": "~2.0.0"
  }
}

然後 npm 會自動讀取 package.json


npm install

就會幫我們 mongoDB 裝好了

安裝測試工具-mocha

我們是好孩子,所以要寫 unit test,詳細的介紹在 NODE_TEST 裡


$ npm install -g mocha

MongoDB CRUD

我們需要先寫 CRUD 的測試

新增一個 test 目錄並建立一個 dbCRUDTest.js 的檔案,程式碼如下:


var dbConnect = require('../bin/dbConnect.js');
var dbConnectTest = new dbConnect();
var crud = require('../bin/dbCRUD.js');
var crudTest = new crud();
var assert = require('assert');

describe('dbTest', function () {
    before('create Test Collection', function (done) {
        console.log('createTestCollection');
        dbConnectTest.connect(function (db) {
            db.createCollection('event', function (err, results) {
                db.close();
                assert.equal(null, err);
                done();
            });
        });
    });

    it('connect Should Be Success', function (done) {
        dbConnectTest.connect(function (db) {
            db.admin().serverInfo(function (err, results) {
                db.close();
                assert.equal(null, err);
                done();
            });
        });
    });

    it('insert Should Be Success', function (done) {
        dbConnectTest.connect(function (db) {
            crudTest.insert({ userId: '1234', event: 'test' }, db, function (err, results) {
                db.close();
                if (err) throw err;
                done();
            });
        });
    });

    it('select Should Not Be 0', function (done) {
        dbConnectTest.connect(function (db) {
            crudTest.select(null, db, function (cursor) {
                cursor.count(function (err, count) {
                    db.close();
                    assert.equal(null, err);
                    assert.notEqual(count, 0);
                    done();
                });

            });
        });
    });

    it('update Should Be Success. But Update Nothing', function (done) {
        dbConnectTest.connect(function (db) {
            crudTest.update({ _id: 1, event: 'test2', userId: '1234' }, db, function (err, results) {
                db.close();
                assert.equal(null, err);
                assert.equal(0,results.modifiedCount)
                done();
            });
        });
    });

    it('delete Should Be Success. But Delete Nothing', function (done) {
        dbConnectTest.connect(function (db) {
            crudTest.delete({ _id: 1, userId: '1234' }, db, function (err, results) {
                db.close();
                assert.equal(null, err);
                assert.equal(0,results.deletedCount)
                done();
            });
        });
    });

    after('drop Test Collection', function (done) {
        console.log('dropTestCollection');
        dbConnectTest.connect(function (db) {
            db.dropCollection('event', function (err, results) {
                db.close();
                assert.equal(null, err);
                done();
            });
        });

    });
});

做測試之前我們需要先把 mongodb 打開

在 Ubuntu 上 MongoDB 開機後便會自動開啟. 在 Mac 上你需要手動輸入下面的指令.


$ mongod

在bin/下新增一個檔案叫做 dbConnect.js 來設定 MongoDB


/**
 * Name:dbConnect.js 
 * Purpose:connect mongodb 
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-15
 */

module.exports = function () {
    var MongoClient = require('mongodb').MongoClient;//mongodb client
    var url = 'mongodb://localhost:27017/todo_list_test';// mongodb://登入url/db名稱

    this.connect = function (callback) {
        MongoClient.connect(url, function (err, db) {
            if (err) {
                throw err;
            } else {
                callback(db);
            }
        });
    }    
}

在 bin/ 下新增一個檔案叫做 dbCRUD.js,這是給 todo_list 讀寫資料庫用的。


/**
 * Name:dbCRUD.js 
 * Purpose:todo_list CRUD
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-15
 */

module.exports = function () {

}

進行第一次測試,請切換到 todo_list 目錄底下,執行以下指令


$ mocha test/dbCRUDTest.js

如上圖所示,你可以注意到雖然 connect 過了,但 是CRUD 沒有過,因為我們還未定義 dbCRUD.js 的 function,讓我們把洞補起來。

修改 dbCRUD.js 如下


/**
 * Name:dbCRUD.js 
 * Purpose:todo_list CRUD
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-15
 */

module.exports = function () {

    this.select = function (findCondition, db, callback) {
        var cursor = db.collection('event').find(findCondition);
        callback(cursor);
    }

    this.insert = function (insertObject, db, callback) {
        db.collection('event').insertOne({event:insertObject.event, userId:insertObject.userId}, function (err, results) {
            callback(err, results);
        });
    }

    this.update = function (updateObject, db, callback) {
        var ObjectID = require('mongodb').ObjectID
        var id = new ObjectID(updateObject.id);
        db.collection('event').updateOne({_id:id, userId:updateObject.userId}, {$set:{event:updateObject.event}} ,function (err, results) {
            callback(err, results);
        });
    }

    this.delete = function (deleteObject, db, callback) {
        var ObjectID = require('mongodb').ObjectID
        var id = new ObjectID(deleteObject.id);
        db.collection('event').deleteOne({_id:id, userId:deleteObject.userId}, function (err, results) {
            callback(err, results);
        });
    }
}

執行第二次測試如下


$ mocha test/dbCRUDTest.js

這次就全數通過了。

  • 備註

這個測試,有兩個不好的地方。

  1. connect 跟所有相依在一起了
  2. insert 跟 select 相依在一起了

理論上應該要新增一個假的 db instance 去做測試,但是因為作者懶惰的關係,所以決定這樣寫。

修改 index.js 使他可以查詢

我們需要調整 index.js,讓他可以帶查詢的結果

index.js 修改程式碼如下:


/**
 * Name:index.js 
 * Purpose:show index.html
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-20
 */

var express = require('express');
var router = express.Router();

var dbConnect = require('../bin/dbConnect.js');
var dbConn = new dbConnect();

var dbCRUD = require('../bin/dbCRUD.js');
var dbCRUDMethod = new dbCRUD();

/* GET home page. */
router.get('/', function (req, res, next) {
  dbConn.connect(function (db) {
    dbCRUDMethod.select(null, db, function (cursor) {
      var data = [];
      cursor.forEach(function(result){
        data.push(result);
        db.close();
      },function(err){
        if(err) throw err;
        res.render('index', { title: 'Express', cursor: data });
      });

    });
  });

});

module.exports = router;

修改 index.ejs 如下:


<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <ul>
    <% cursor.forEach(function(data){ %>
      <%= data.event %>
    <% }); %>
  </ul>
  </body>
</html>

執行:

DEBUG=todo_list npm start

http://localhost:3000/即可看到成果

這時還沒任何顯示任何資料,因為資料庫裡尚未儲存任何資料。

修改todo_list使其有新增功能


<!DOCTYPE html>
<html>

<head>
  <title>
    <%= title %>
  </title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
  <p>Welcome to <%= title %></p>
  <form method="post" action="insert">
    <input type="text" value="" placeholder="請輸入代辦事項" />
    <button type="submit" value="">送出</button>
  </form>
  <ul>
    <% cursor.forEach(function(data){ %>
      <%= data.event %>
        <% }); %>
  </ul>
</body>

</html>

app.js:


var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);
app.use('/control/:method', control);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});


module.exports = app;

新增一個control.js檔案在routes底下

/**
 * Name:control.js 
 * Purpose:update insert delete todo_list
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-21
 */

var express = require('express');
var router = express.Router();

/* insert home page. */
router.post('/', function (req, res, next) {
  var Db = require('../bin/DbConnect.js');
  var dbConn = new Db();
  var dbCRUD = require('../bin/dbCRUD.js');
  var dbCRUDControl = new dbCRUD();
  dbConn.connect(function (db) {
    switch (req.query.method) {
      case 'insert':
        dbCRUDControl.insert({ event: req.body.event, userId: 1234 }, db, function (err, results) {
          if (err) throw err;
          db.close();
          res.redirect('/');
        });
        break;
      default:
        res.redirect('/');
        break;
    }
  });
});

module.exports = router;

執行:

DEBUG=todo_list npm start

http://localhost:3000/ 即可看到成果

完成修改、刪除功能。

index.ejs:


<!DOCTYPE html>
<html>

<head>
  <title>
    <%= title %>
  </title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">

  <!-- Latest jquery  -->
  <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>

  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>

<body>
  <div class="row">
    <p>Welcome to
      <%= title %>
    </p>
  </div>
  <div class="row">
    <form method="post" action="control?method=insert" class="form-inline">
      <input type="text" name="event" value="" placeholder="請輸入代辦事項" />
      <button type="submit" class="btn btn-primary" value="">送出</button>
    </form>
  </div>
  <div class="row">
    <table class="table">
      <thead>
        <tr>
          <td>待辦事項</td>
          <td>功能</td>
        </tr>
      </thead>
      <tbody
      <% cursor.forEach(function(data){ %>
        <tr>
          <td>
            <%= data.event %>
          </td>
          <td>
            <button type="button" class="btn btn-default" onclick="showModal('<%= data.event %>','<%= data._id %>','update')">修改</button>
            <button type="button" class="btn btn-default" onclick="showModal('','<%= data._id %>','delete')">刪除</button>
          </td>
        </tr>
        <% }); %>
      </tbody>
    </table>
  </div>

  <!-- modal for update -->
  <div class="modal fade" id="updateModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title">修改代辦事項</h4>
        </div>
        <form method="post" action="control?method=update">
          <div class="modal-body">
            <input type="text" value="" name="updateImportEventText" id="updateImportEventText" />
            <input type="hidden" value="" name="updateImportEventId" id="updateImportEventId" />
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            <button type="submit" class="btn btn-warning">Save</button>
          </div>
        </form>
      </div>
      <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
  </div>
  <!-- /.modal -->

  <!-- modal for delete -->
  <div class="modal fade" id="deleteModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title">刪除代辦事項</h4>
        </div>
        <form method="post" action="control?method=delete">
          <div class="modal-body">
            <label>是否刪除</label>
            <input type="hidden" value="" name="deleteImportEventId" id="deleteImportEventId" />
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">否</button>
            <button type="submit" class="btn btn-warning">是</button>
          </div>
        </form>
      </div>
      <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
  </div>
  <!-- /.modal -->
</body>

<script>
  function showModal(event, id, method){
    switch (method){
      case 'update':
      $('#updateImportEventId').val(id);
      $('#updateImportEventText').val(event);
      $('#updateModal').modal('show');
      break;
      case 'delete':
      $('#deleteImportEventId').val(id);
      $('#deleteModal').modal('show');
      break;
    }
  }

</script>

</html>

control.js:


/**
 * Name:control.js 
 * Purpose:update insert delete todo_list
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-21
 */

var express = require('express');
var router = express.Router();

/* insert home page. */
router.post('/', function (req, res, next) {
  var Db = require('../bin/DbConnect.js');
  var dbConn = new Db();
  var dbCRUD = require('../bin/dbCRUD.js');
  var dbCRUDControl = new dbCRUD();
  dbConn.connect(function (db) {
    switch (req.query.method) {
      case 'insert':
        dbCRUDControl.insert({ event: req.body.event, userId: 1234 }, db, function (err, results) {
          if (err) throw err;
          db.close();
          res.redirect('/');
        });
        break;
      case 'update':
        dbCRUDControl.update({ event: req.body.updateImportEventText, id: req.body.updateImportEventId, userId: 1234 }, db, function (err, results) {
          if (err) throw err;
          db.close();
          res.redirect('/');
        });
        break;
      case 'delete':
        dbCRUDControl.delete({ id: req.body.deleteImportEventId, userId: 1234 }, db, function (err, results) {
          if (err) throw err;
          db.close();
          res.redirect('/');
        });
        break;
      default:
        res.redirect('/');
        break;
    }
  });
});

module.exports = router;

我在 HTML 裡引入了 bootstrap 跟 jquery 以增進使用者體驗跟美化。 並且在 control.js 裡增加了 delete 跟 update 方法。

你可以試著運行看看結果如何。

passport.js 安裝

你可以注意到我們的 userid 都是寫死的,這樣無法區別使用者,所以這邊我們將運用 passport.js 做 facebook 登入。

修改 package.json:


{
  "name": "todo_list",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.13.2",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "ejs": "~2.3.3",
    "express": "~4.13.1",
    "morgan": "~1.6.1",
    "serve-favicon": "~2.3.0",
    "mongodb": "~2.0.0",
    "cookie-session":"~2.0.0",
    "passport":"~0.3.0",
    "passport-facebook":"2.0.0"
  }
}

passport 需要 session 儲存使用者資訊,這裡我們選擇 cookie-session,還需要擴充模組以支援 facebook 登入

執行:


$ npm install
  • facebook APP 申請

登入 https://developers.facebook.com,點選上方選單的 My apps->Add a New App

點選 Settings,切換到上方的 Advanced 分頁。

往下尋找 Valid OAuth redirect URIs,填寫如圖

拉到最下按 save 就完成了。

  • 附註

如果你想要提供給其他人登入的話必須將下圖的電子郵件填寫完畢後

status & Review 中的 status 點選 No 使其變成 Yes

app.js:


var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var cookieSession = require('cookie-session');

//facebbok login
var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy

var FACEBOOK_APP_ID = "--insert-facebook-app-id-here--"
var FACEBOOK_APP_SECRET = "--insert-facebook-app-secret-here--";

// Passport session setup.
//   To support persistent login sessions, Passport needs to be able to
//   serialize users into and deserialize users out of the session.  Typically,
//   this will be as simple as storing the user ID when serializing, and finding
//   the user by ID when deserializing.  However, since this example does not
//   have a database of user records, the complete Facebook profile is serialized
//   and deserialized.
passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(function (obj, done) {
  done(null, obj);
});


// Use the FacebookStrategy within Passport.
//   Strategies in Passport require a `verify` function, which accept
//   credentials (in this case, an accessToken, refreshToken, and Facebook
//   profile), and invoke a callback with a user object.
passport.use(new FacebookStrategy({
  clientID: FACEBOOK_APP_ID,
  clientSecret: FACEBOOK_APP_SECRET,
  callbackURL: "http://localhost:3000/auth/facebook/callback"
},
  function (accessToken, refreshToken, profile, done) {
    // asynchronous verification, for effect...
    process.nextTick(function () {

      // To keep the example simple, the user's Facebook profile is returned to
      // represent the logged-in user.  In a typical application, you would want
      // to associate the Facebook account with a user record in your database,
      // and return that user instead.
      return done(null, profile);
    });
  }
  ));

var routes = require('./routes/index');
var users = require('./routes/users');
var control = require('./routes/control');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(cookieSession({ secret: 'I am your FATHER' }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());


app.use('/', routes);
app.use('/users', users);
app.use('/control', control);

// GET /auth/facebook
app.get('/auth/facebook',
  passport.authenticate('facebook'),
  function (req, res) {
    // The request will be redirected to Facebook for authentication, so this
    // function will not be called.
  });

// GET /auth/facebook/callback
app.get('/auth/facebook/callback',
  passport.authenticate('facebook', { failureRedirect: '/' }),
  function (req, res) {
    res.redirect('/');
  });

app.get('/logout', function (req, res) {
  req.logout();
  res.redirect('/');
});

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

module.exports = app;

index.ejs


<!DOCTYPE html>
<html>

<head>
  <title>
    待辦事項
  </title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">

  <!-- Latest jquery  -->
  <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>

  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>

<body>
  <% if (name) { %>
  <div class="row">
    <label>Welcome to <%= name %></label><a class="btn btn-warning btn-xs" href="/logout">登出</a>
  </div>
  <div class="row">
    <form method="post" action="control?method=insert" class="form-inline">
      <input type="text" name="event" value="" placeholder="請輸入待辦事項" />
      <button type="submit" id="submit" class="btn btn-default" value="">送出</button>
    </form>
  </div>
  <% }else{ %>
  <div class="row" id="LoginMessage">
    <div>請先登入FB</div>
    <div>
      <a class="btn btn-primary" href="/auth/facebook">
      Facebook Login
      </a>
    </div>
  </div>
  <% } %>
  <div class="row">
    <table class="table">
      <thead>
        <tr>
          <td>待辦事項</td>
          <td>功能</td>
        </tr>
      </thead>
      <tbody>
        <% if (cursor) { %>
        <% cursor.forEach(function(data){ %>
        <tr>
          <td>
            <%= data.event %>
          </td>
          <td>
            <button type="button" class="btn btn-default" onclick="showModal('<%= data.event %>','<%= data._id %>','update')">修改</button>
            <button type="button" class="btn btn-default" onclick="showModal('','<%= data._id %>','delete')">刪除</button>
          </td>
        </tr>
        <% }); %>
        <% } %>
      </tbody>
    </table>
  </div>

  <!-- modal for update -->
  <div class="modal fade" id="updateModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title">修改代辦事項</h4>
        </div>
        <form method="post" action="control?method=update">
          <div class="modal-body">
            <input type="text" value="" name="updateImportEventText" id="updateImportEventText" />
            <input type="hidden" value="" name="updateImportEventId" id="updateImportEventId" />
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            <button type="submit" class="btn btn-warning">Save</button>
          </div>
        </form>
      </div>
      <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
  </div>
  <!-- /.modal -->

  <!-- modal for delete -->
  <div class="modal fade" id="deleteModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title">刪除代辦事項</h4>
        </div>
        <form method="post" action="control?method=delete">
          <div class="modal-body">
            <label>是否刪除</label>
            <input type="hidden" value="" name="deleteImportEventId" id="deleteImportEventId" />
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal"></button>
            <button type="submit" class="btn btn-warning"></button>
          </div>
        </form>
      </div>
      <!-- /.modal-content -->
    </div>
    <!-- /.modal-dialog -->
  </div>
  <!-- /.modal -->
</body>

<script>
  function showModal(event, id, method){
    switch (method){
      case 'update':
      $('#updateImportEventId').val(id);
      $('#updateImportEventText').val(event);
      $('#updateModal').modal('show');
      break;
      case 'delete':
      $('#deleteImportEventId').val(id);
      $('#deleteModal').modal('show');
      break;
    }
  }

</script>

</html>

index.js


/**
 * Name:index.js 
 * Purpose:show index.html
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-20
 */

var express = require('express');
var router = express.Router();

var dbConnect = require('../bin/dbConnect.js');
var dbConn = new dbConnect();

var dbCRUD = require('../bin/dbCRUD.js');
var dbCRUDMethod = new dbCRUD();


/* GET home page. */
router.get('/', function (req, res) {
  dbConn.connect(function (db) {
    if (typeof req.user == 'undefined') {
      res.render('index', { name: false, cursor: false });
    } else {
      dbCRUDMethod.select({userId:req.user.id}, db, function (cursor) {
        var data = [];
        cursor.forEach(function (result) {
          data.push(result);
          db.close();
        }, function (err) {
          if (err) throw err;
          res.render('index', { name: req.user.displayName, cursor: data });

        });

      });
    }

  });

});

module.exports = router;

control.js


/**
 * Name:control.js 
 * Purpose:update insert delete todo_list
 * Author:Yun 
 * Version:1.0
 * Update:2015-10-21
 */

var express = require('express');
var router = express.Router();

/* insert home page. */
router.post('/', function (req, res, next) {
  var Db = require('../bin/DbConnect.js');
  var dbConn = new Db();
  var dbCRUD = require('../bin/dbCRUD.js');
  var dbCRUDControl = new dbCRUD();
  if (typeof req.user != 'undefined') {
    dbConn.connect(function (db) {
      switch (req.query.method) {
        case 'insert':
          dbCRUDControl.insert({ event: req.body.event, userId: req.user.id }, db, function (err, results) {
            if (err) throw err;
            db.close();
            res.redirect('/');
          });
          break;
        case 'update':
          dbCRUDControl.update({ event: req.body.updateImportEventText, id: req.body.updateImportEventId, userId: req.user.id }, db, function (err, results) {
            if (err) throw err;
            db.close();
            res.redirect('/');
          });
          break;
        case 'delete':
          dbCRUDControl.delete({ id: req.body.deleteImportEventId, userId: req.user.id }, db, function (err, results) {
            if (err) throw err;
            db.close();
            res.redirect('/');
          });
          break;
        default:
          res.redirect('/');
          break;
      }
    });
  } else {
    res.redirect('/');
  }
});

module.exports = router;

最後畫面:

完成

恭喜你,你已經跨出了 Node.js 的第一步,歡迎你加入 Node.js 這個大社群。