Node.js 與 Socket.io – 即時聊天室實作:資料庫

經過前兩篇()文章,我們的聊天室現在有了一個可以記憶的小腦袋,不過進過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 吧。

完成!

接下來要弄一個帳號給這個這個資料庫。

這樣我們等等才有辦法連到這個資料庫使用。

資料庫連線

終於要開始正題啦!

安裝套件

nom install --save mongoose

在這個教學中,我們選擇使用 Mongoose ODM 這個套件,而不使用 MongoDB 的原生套件的原因是,我覺得有點難用,不過原生的套件比較單純,且也比較貼近 MongoDB 的原生用法,各有優劣。

建立連線

裝好套件之後,在我們先前的聊天室專案資料夾中開一個新檔案叫做 db-connector.js,輸入以下程式:

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>有沒有輸入錯誤。

一切就緒沒問題的話,我們將這個程式修改成:

const mongoose = require('mongoose');
mongoose.connect('這邊改成 mlab 資料庫位址');

module.exports = mongoose.connection;

這樣這個連線資訊才能方便地在其他程式間使用。

Data Model 設計

Mogoonse 的 Data Model 資料模型特性可以讓你在資料庫的設計上更為嚴謹些。因為在原始的 MongoDB 設計中,資料的型別可以是任意值,而這往往會造成不預期的資料內容。雖然原生的 MongoDB 也有提供資料模型可以做型別驗證,但 Mongoose 提供的方法更簡單好用。

雖然這在我們單純的聊天室中用途不大,不過透過這樣的設計,我們可以比較好掌握我們到底存了什麼樣的資料到資料庫中,未來除錯也會方便許多。雖然這會失去 MongoDB 它自由儲存內容的特性就是了。

那麼在建立資料模型前,我們得先定義資料表(詳細可參考:Schema)。

在聊天室專案資料夾中建立一個schema.js的檔案,並把以下內容輸入:

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,加入這些程式碼

const {EventEmitter} = require("events");
// 加入下面這些
const mongoose = require('./db-connector');
const schema = require('./schema');

const Message = mongoose.model('Message', schema);

然後修改push方法

// 將聊天資料轉成資料模型
const m = new Message(msg);
// 存至資料庫
m.save();

this.emit("new_message", msg);

if (data.length > MAX) {
    data.splice(0, 1);
}

再來是get

get (callback) {
    // 取出所有資料
    Message.find((err, msgs) => {
        callback(msgs);
    });
}

最後修改index.js

// socket.emit("chatRecord", records.get()); // 砍掉這行
// 改成下面這個
records.get((msgs) => {
    socket.emit("chatRecord", msgs);
});

好了!這樣你就得到了一個聊天資料是存到資料庫中的聊天室了!

趕快啟動伺服器試試看,輸入幾個聊天資料後,關閉伺服器再重啟,看看先前輸入的聊天資料會不會再一次出現吧~

刪除多餘資料

等等,我們當初在設計聊天記錄的功能時,有加入最大上限(MAX)的設計,那麼資料庫也應該要把這問題考慮進去。

讓我們回到records.jspush方法

// 刪除這段
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 連線資訊是無法直接使用的

獨夜:

看評論 (2)

你可能也會有興趣