經過前兩篇(一、二)文章,我們的聊天室現在有了一個可以記憶的小腦袋,不過進過Demo的人大概都會有個疑惑:「說好的紀錄呢?怎麼都是空白的?」
這是因為 Heroku 免費方案會把閒置的伺服器程式切入休眠狀態,當有外部請求時才會重新啟動(這也是為什麼Demo頁面偶爾啟動會很慢的原因),而我們的記憶體資料也會在此時被重置成像新的一樣。
所以,我們不能把資料單單存在記憶體中,我們現在要來把資料放到「資」、「料」、「庫」!
我相信現在大家對於 Node.js、Socket.io 都有了初步的了解,接下來筆者我就要帶各位見見我們今天主角資料庫啦~
現代網站多數都擁有一個儲存眾多資料的地方,我們稱之為資料庫。通常為一個伺服器程式,提供一個或多個連接介面給程式開發者與資料庫伺服器互動,例如資料新增、查詢、修改、刪除等,而這四項操作又稱之為CRUD,Create、Read、Update、Delete,是資料的常用操作。
今天我們將要採用的資料庫系統是 MongoDB,我們將透過 mlab 這個免費服務來帶大家幫我們的聊天室服務加上資料庫的儲存功能。
mLab
mLab 是一個免費的 MongoDB 線上資料庫服務,他最低的沙盒免費方案提供我們 0.5GB 的儲存容量,這對於學習與實驗的我們其實就相當夠用了,還免除了繁雜的 MongoDB 安裝步驟。在 mLab 我們只需要申請帳號、設定資料庫、啟動!就能開始操作啦~
申請帳號
使用 mLab 之前我要先申請一個帳號,點畫面右上方的 SIGN UP 或點這裡進入申請畫面後,依照畫面指示填入資料就可以了。
設定資料庫
申請好帳號並登入 mLab 服務之後,我們就要來建立一個資料庫啦~
請點畫面右方的 Create new
接下來會出現雲端服務供應商,你可以選擇AWS、GCP或Azure,這邊就依個人喜好選擇就行了,不過第二項的 Plan Type 只有第一個 SANDBOX 才是免費的。選好之後點右下方的 Continue 進入下一步。
然後是選擇區域,就選美國吧。
輸入資料庫名稱,這邊我就叫做 s9test 吧。
完成!
接下來要弄一個帳號給這個這個資料庫。
這樣我們等等才有辦法連到這個資料庫使用。
資料庫連線
終於要開始正題啦!
安裝套件
1 2 |
nom install --save mongoose |
在這個教學中,我們選擇使用 Mongoose ODM 這個套件,而不使用 MongoDB 的原生套件的原因是,我覺得有點難用,不過原生的套件比較單純,且也比較貼近 MongoDB 的原生用法,各有優劣。
建立連線
裝好套件之後,在我們先前的聊天室專案資料夾中開一個新檔案叫做 db-connector.js
,輸入以下程式:
1 2 3 4 5 6 7 8 9 10 |
const mongoose = require('mongoose'); mongoose.connect('這邊改成 mlab 資料庫位址'); const db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function() { console.log('connected!'); }); |
mlab 資料庫位址在剛剛建立資料庫時的畫面上方這邊
然後試著執行看看,應該會看到一個connected!
的訊息,如果沒有的話,檢查一下<dbuser>
和<dbpassword>
有沒有輸入錯誤。
一切就緒沒問題的話,我們將這個程式修改成:
1 2 3 4 5 |
const mongoose = require('mongoose'); mongoose.connect('這邊改成 mlab 資料庫位址'); module.exports = mongoose.connection; |
這樣這個連線資訊才能方便地在其他程式間使用。
Data Model 設計
Mogoonse 的 Data Model 資料模型特性可以讓你在資料庫的設計上更為嚴謹些。因為在原始的 MongoDB 設計中,資料的型別可以是任意值,而這往往會造成不預期的資料內容。雖然原生的 MongoDB 也有提供資料模型可以做型別驗證,但 Mongoose 提供的方法更簡單好用。
雖然這在我們單純的聊天室中用途不大,不過透過這樣的設計,我們可以比較好掌握我們到底存了什麼樣的資料到資料庫中,未來除錯也會方便許多。雖然這會失去 MongoDB 它自由儲存內容的特性就是了。
那麼在建立資料模型前,我們得先定義資料表(詳細可參考:Schema)。
在聊天室專案資料夾中建立一個schema.js
的檔案,並把以下內容輸入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const mongoose = require('mongoose'); const Schema = mongoose.Schema; module.exports = new Schema({ name: { // 欄位名稱 type: String, // 欄位資料型別 required: true, // 必須要有值 }, msg: { type: String, required: true, }, time: { type: Date, required: true } }); |
我們在這個檔案中定義了資料儲存的基本規範,我們要求了三個欄位,並且指定他們的資料型別(type),然後都設定成必要輸入的內容,不可缺少。
設定可以參考官方的文件:http://mongoosejs.com/docs/validation.html
存入與取出資料
定義完聊天室的資料模型後,我們要來寫程式將聊天資料從伺服器記憶體存到資料庫中啦~
打開records.js
,加入這些程式碼
1 2 3 4 5 6 7 |
const {EventEmitter} = require("events"); // 加入下面這些 const mongoose = require('./db-connector'); const schema = require('./schema'); const Message = mongoose.model('Message', schema); |
然後修改push
方法
1 2 3 4 5 6 7 8 9 10 11 |
// 將聊天資料轉成資料模型 const m = new Message(msg); // 存至資料庫 m.save(); this.emit("new_message", msg); if (data.length > MAX) { data.splice(0, 1); } |
再來是get
1 2 3 4 5 6 7 |
get (callback) { // 取出所有資料 Message.find((err, msgs) => { callback(msgs); }); } |
最後修改index.js
1 2 3 4 5 6 |
// socket.emit("chatRecord", records.get()); // 砍掉這行 // 改成下面這個 records.get((msgs) => { socket.emit("chatRecord", msgs); }); |
好了!這樣你就得到了一個聊天資料是存到資料庫中的聊天室了!
趕快啟動伺服器試試看,輸入幾個聊天資料後,關閉伺服器再重啟,看看先前輸入的聊天資料會不會再一次出現吧~
刪除多餘資料
等等,我們當初在設計聊天記錄的功能時,有加入最大上限(MAX)的設計,那麼資料庫也應該要把這問題考慮進去。
讓我們回到records.js
的push
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 刪除這段 if (data.length > MAX) { data.splice(0, 1); } // 加入這段 // 取得資料庫中有多少筆紀錄 Message.count().then((count) => { if (count >= MAX) { Message.find().sort({'time': 1}).limit(1).then((res) => { // 找到最舊的那個訊息 Message.findByIdAndRemove(res[0]._id); // 然後移除 }); } }); |
好了,現在你不需要擔心資料庫會塞太多歷史垃圾而漫出來了!
碎念之,這篇文章其實要把一些錯誤 catch 起來,不過寫起來太醜了,我就沒放了(遭毆)
DEMO
結
這一篇,算是系列文的最後一篇了。
這三篇文章從什麼都沒有到作出一個擁有記錄功能的聊天室,雖然功能還是很單純、很陽春,卻有著她美好的一面。因為她很簡單,很輕巧。
希望這幾篇文章能給予你任何對於 Node.js、Socket.io、MongoDB 學習上的一點幫助。
對了,程式碼有放在 GitHub 上,歡迎翻閱:https://github.com/single9/simple-chat-room
謝謝大家!
– GitHub 上的 DB 連線資訊是無法直接使用的
Hi~ 謝謝你的這三篇 socket.io 應用教學,講解得很清楚,幫助很大!! 提醒一下,最後這篇少了 index.html 要加上 time 欄位資料喔
謝謝提醒!