iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 19
0
Modern Web

Half-Stack Developer 養成計畫系列 第 19

沒那麼簡單~的留言板

沒那麼簡單~的留言板

如果講了資料庫卻又不帶你真的跑一次流程,我這個作者就有點太混了。原本,我是想在這一篇裡面同時教 MySQL 跟 MongoDB(NoSQL),但是考量到 MySQL 的環境建置有一點麻煩,怕大家搞不定,於是就先作罷了。等我這三十天順利度過,有把文章全部完成,再找個時間來擴充內容。

MongoDB 是一個相當知名的 NoSQL 資料庫系統,也是我們今天的主角。官網上面還有個MongoDB University,提供了一堆的教學。

接觸新東西的第一步,也就是最困難的一步,就是建設環境。幸好他們提供的官方教學寫得很詳細,而且要提供了很方便的工具。例如說如果你是用 Mac,教學就告訴你說你只要 brew install mongodb 就好了。安裝完成之後用mkdir -p /data/db建立一個讓他可儲存資料的資料夾,最後用mongod這個指令就可以順利啟動資料庫。(弱勢你在使用任何指令的時候碰到權限問題,試著在前面加上sudo,用系統管理員的身份執行看看,像我就是用sudo mkdir -p /data/dbsudo mongod才順利跑起來)

如果你在過程中碰到什麼其他的困難,請愛用谷歌大神、Stackoverflow 以及官方文件。這些資源都是有用的,比我有用一百倍。

當你順利跑起來以後,應該要看到類似的畫面:

http://ithelp.ithome.com.tw/upload/images/20161227/20091346PsLNRtszzO.png

接著我們直接來抄官方教學的程式碼來寫一個簡單的的跟 DB 連結的小程式。

記得要先安裝 mongodb 的 library:

npm install mongodb --save

然後新建一個 index.js

var MongoClient = require('mongodb').MongoClient;

// 要連接的網址
var url = 'mongodb://localhost:27017/myproject';

// 連接到 DB
MongoClient.connect(url, function(err, db) {
  console.log("Connected successfully to server");

  // 插入新的資料
  insertDocuments(db, function() {
    db.close();
  });
});

var insertDocuments = function(db, callback) {

  var collection = db.collection('documents');

  // 寫入資料
  collection.insertMany([
    {a : 1}, {a : 2}, {a : 3}
  ], function(err, result) {

    // callback 回傳結果
    console.log("Inserted 3 documents into the collection");
    callback(err, result);
  });
}

node index.js 執行一下,應該會看到 log 跟你說插入成功了。可是資料寫入成功之後,我們要怎麼知道是不是真的成功呢?

先去下載一個叫做robomongo的程式,打開之後連線到 local 的資料庫,你就可以看到我們剛剛塞進去的資料了。第一次進去的時候要按下「Create」設定一下資料。

http://ithelp.ithome.com.tw/upload/images/20161227/20091346wTFBjteiKL.png
接著左邊照我這樣點進去之後就可以看到資料了:

http://ithelp.ithome.com.tw/upload/images/20161227/20091346RKvzdKwlMX.png

這個就代表我們剛剛有成功寫入資料。

之前忘記講,因為 SQL 跟 NoSQL 儲存資料的方式不一樣,所以資料庫的指令也完成不一樣。概念都類似,但語法差滿多的,所以你需要一點時間適應一下。但某種程度上來說,他的指令會比 SQL 好懂一點。你剛剛已經學會插入的指令了,接下來來看一下讀取、更新跟刪除吧!

var findDocuments = function(db, callback) {
  // Get the documents collection
  var collection = db.collection('documents');
  // Find some documents
  collection.find({}).toArray(function(err, docs) {
    console.log("Found the following records");
    console.log(docs)
    callback(docs);
  });
}

只要用 collection.find({}),就可以取出所有的資料。這邊 find 函數傳進去的參數是你要搜尋的條件,空物件代表沒有指定任何條件,所以所有的資料都會被搜出來。

var updateDocument = function(db, callback) {
  // Get the documents collection
  var collection = db.collection('documents');
  // Update document where a is 2, set b equal to 1
  collection.updateOne({ a : 2 }
    , { $set: { b : 1 } }, function(err, result) {
    console.log("Updated the document with the field a equal to 2");
    callback(result);
  });  
}

Update 的指令是這樣,第一個參數指定你要條件,看你要選中哪一筆資料。第二個條件就是你要設定成什麼。這邊會新增一個 b:1的資料。

var removeDocument = function(db, callback) {
  // Get the documents collection
  var collection = db.collection('documents');
  // Insert some documents
  collection.deleteOne({ a : 3 }, function(err, result) {
    console.log("Removed the document with the field a equal to 3");
    callback(result);
  });    
}

刪除就更簡單一點了,你後面就直接指定條件就好。像這邊就是要刪除a=3的資料。

既然你現在已經學會基本的 CRUD 操作,而且也知道怎麼讓 mongodb 跑起來了。我們就可以來試試看把之前那個用檔案為基礎的資料庫,換成用 mongodb 當作資料庫了。而且跟你說,有一件很棒的事情。

我們之前不是把跟資料庫有關的操作,全部寫在 db.js 裡面嗎?所以我們只要改這個檔案就好了,其他的程式碼完全不用動到!是不是很棒的一件事!如果你有天發現你要改一個地方,卻要動一大堆檔案跟程式碼,那你就可以開始想想看有沒有更好的架構可以改進了。寫程式的原則就是盡量讓每一個檔案都很簡單,這樣你要改的時候才會比較好改。

var MongoClient = require('mongodb').MongoClient;
var ObjectId = require('mongodb').ObjectId;

// 要連接的網址
var url = 'mongodb://localhost:27017/myproject';
var db = null;

var DB = {
  connect: function (cb) {

    // 連接到 DB
    MongoClient.connect(url, function(err, mongo) {
      console.log("Connected successfully to server");
      db = mongo;
      cb(err);
    });
  },

  addPost: function (post, cb) {
    var collection = db.collection('documents');

    // 寫入資料
    collection.insert(post, function(err, result) {
      cb(err, result);
    });
  },

  deletePost: function (id, cb) {
    var collection = db.collection('documents');

    collection.deleteOne({ _id : ObjectId(id) }, function(err, result) {
      console.log(err, result);
      cb(err, result);
    }); 
  },

  getPosts: function (cb) {
    var collection = db.collection('documents');

    collection.find({}).toArray(function(err, docs) {
      cb(err, docs);
    });
  }
}

module.exports = DB;

這邊有幾個小細節必須注意,第一個是 post 的 id,我們之前不是隨機亂數產生嗎?可是有了 mongodb 以後,他就會在寫入資料的時候自動幫你產生一個 ObjectId,所以這部分不用自己做,交給資料庫系統來做就好。這也是為什麼deletePost裡面的條件要加上ObjectId(id),如果不加的話會找不到。

第二個小細節是新增了一個叫做 connect 的 function。這是為什麼呢?因為連接到資料庫是非同步的行為,你啟動 http server 也是非同步的行為。你沒辦法保證哪一個會先完成。所以這邊必須新增一個連接的 function,確保我們在連接到資料庫以後才啟動 http server。

// 舊的程式碼長這樣
app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

// 新的會長這樣,連接資料庫以後才啟動
db.connect(function (err) {
  if(!err) {
    app.listen(3000, function () {
      console.log('Example app listening on port 3000!')
    })
  }
})

第三個小細節是我們原本是用自己的 id,而 mongo 自動幫我們新建的會叫做:_id,所以你要在 index.ejs 裡面把出現 id 的地方 換成 _id。然後在 index.js 裡面,新增文章的 function 就不必自己產生 id 了。

我直接再附上小幅改動後的 index.js 檔案:

var express = require('express');
var session = require('express-session')
var bodyParser = require('body-parser')
var db = require('./db');

var app = express();

// 使用 session,要設定一個 secret key
app.use(session({
  secret: 'keyboard cat',
}))

// 有了這個才能透過 req.body 取東西
app.use(bodyParser.urlencoded({ extended: false }))

app.set('view engine', 'ejs');

// 首頁,直接輸出所有留言
app.get('/', function (req, res) {

  // 試著看看 session 裡面有沒有 username 可以拿
  // 判斷是否是管理員
  var username = req.session.username;
  var isAdmin = false;
  if (username) {
    isAdmin = true;
  }

  // 拿出所有的留言
  db.getPosts(function (err, posts) {
    if (err) {
      res.send(err);
    } else {

      // 記得要把 posts 反過來,才是正確的順序
      // 把所有東西丟給 ejs 去處理
      res.render('index', {
        username: username,
        isAdmin: isAdmin,
        posts: posts.reverse()
      });
    }
  })
});

// 刪除文章
app.get('/posts/delete/:id', function (req, res) {
  var id = req.params.id;
  db.deletePost(id, function (err) {
    if (err) {
      res.send(err);
    } else {

      // 成功後導回首頁
      res.redirect('/');
    }
  })
})

// 發表新文章的頁面
app.get('/posts', function (req, res) {
  res.render('newpost');
})

// 新增文章
app.post('/posts', function (req, res) {
  var author = req.body.author;
  var content = req.body.content;

  // 這邊把自己產生的 id 拿掉了
  db.addPost({
    author: author,
    content: content,
    createTime: new Date(),
  }, function (err, data) {
    if(err) {
      res.send(err)
    } else {
      res.redirect('/');
    }
  })
})

// 輸出登入頁面
app.get('/login', function (req, res) {
  res.render('login');
})

// 登入,如果帳號密碼是 peter 123 就登入通過
app.post('/login', function(req, res) {
  var username = req.body.username;
  var password = req.body.password;
  if (username === 'peter' && password === '123') {
    console.log('login success');
    req.session.username = 'peter';
  }
  res.redirect('/');
})

// 登出,清除 session
app.get('/logout', function(req, res) {
  req.session.destroy();
  res.redirect('/')
})

// 確認先連接到資料庫
db.connect(function (err) {
  if(!err) {
    app.listen(3000, function () {
      console.log('Example app listening on port 3000!')
    })
  }
})

做到這裡,就恭喜你已經會操作資料庫了!只要能完成到這一步,其實就能完成一大堆事情了。因為你會發現所有的後端程式幾乎都只是判斷一下邏輯然後把東西存到資料庫,或者是從資料庫取出來而已。

舉例來說好了,你現在不是可以完成這樣一個很簡單的留言系統嗎?你再改一改就可以做一個部落格系統了,然後還可以加上更多功能,例如說分類、草稿等等的,都只是在資料庫裡面多加一些欄位儲存資料而已。再更進階的話可以寫個論壇系統,每個人都可以發表文章、編輯文章、加上分類,管理員可以刪除文章或是發布公告之類的。但是你會發現底層的概念都是一樣的,都是讀寫資料庫而已。

最後再提一個東西。為了讓大家有自學的能力,我決定只提關鍵字跟給一些資源,至於這個東西到底在做什麼,就需要大家自己研究了:

  1. monngoose
  2. MongoDB 的 ODM:mongoose 簡單介紹

上一篇
閃開!讓專業的來:SQL 與 NoSQL
下一篇
讓我們再轉 180 度,更即時的前端:ajax
系列文
Half-Stack Developer 養成計畫30

尚未有邦友留言

立即登入留言