【實作筆記】利用Node.js及Socket.io製作簡易聊天室﹙下﹚

【實作筆記】利用Node.js及Socket.io製作簡易聊天室﹙下﹚

終於來到這個教學的最後一篇了,經過了前兩篇的教學文章,我們的聊天室現在有一個可以暫存聊天記錄的功能。不過這個雞肋的功能大家細心留意大概都會有個疑惑:「說好的紀錄呢?怎麼重啟伺服器後都是空白的?」。

相信現在大家對於Node.js和以及Socket.io的了解也不會陌生,接下來我就要和各位見見我們這篇教學的主角 — 資料庫。

現代網站多數都擁有一個儲存眾多資料的地方,我們稱之為資料庫。通常為一個伺服器程式,提供一個或多個連接介面給程式開發者與資料庫伺服器互動,例如資料新增、查詢、修改、刪除等,而這四項操作又稱之為CRUD,Create、Read、Update、Delete,是資料的常用操作。

今天我們將要採用的資料庫系統是MongoDB,我們將透過MongoDB Atlas 這個免費服務來帶大家幫我們的聊天室服務加上資料庫的儲存功能。 P.S. 之前網上有很多教學也是以mlab作為資料庫,但自從mlab合併到MongoDB Atlas之後,多多少少設定也有不一樣,所以就藉著這篇教學來教教各位。

所以現在,我們但要把資料存放在記憶體中,還要把它們放在資料庫中,一起來動手看看吧!

上回提要



步驟1: 申請帳號

使用MongoDB Atlas之前我們要先申請一個帳號,填寫基本資料抑或利用Google帳號申請便可。 Register

步驟2: 建立組織和專案

申請好帳號並登入MongoDB Atlas服務之後,我們就要來建立一個組織和所屬的專案啦。 Cluster1

步驟3: 部署你的集群

設定完組織名稱跟專案名稱之後,理應可以看到Cluster﹙集群﹚的設定。就如上圖一樣,之後你便可以選擇所需要的服務。 Cluster2

步驟4: 設定集群的基本資料

當在集群頁面按下Create鍵之後,大家可以看到之後會有幾個選項,當中包括雲端伺服器供應商以及地區。你可以選擇AWSGCPAzure,這邊就依個人喜好選擇就行了﹙我建議大家選AWS作為伺服器供應商而且地區選取美國地區,因為稍後我們會把網站擺放在Heroku上,而基於Heorku的設定,這樣資料傳輸的速度會比較快﹚。 Cluster3

不過第二項的Cluster Tiers,大家將看到可用於共享集群選項的集群層。你可以查看各層之間的記憶體,存儲,虛擬處理器和基本價格的比較,以幫助您選擇合適的層。然而只有第一個M0 Sandbox才是免費的,所以請選取預設的M0沙盒層。 Cluster4

之後也沒有什麼重要設定,集群名稱就隨便地跟它預設的好了。選好之後點右下方的Create Cluster進入下一步。 Cluster5

步驟5: 建立數據庫使用者

Atlas要求客戶端作為MongoDB數據庫用戶進行身份驗證才能訪問群集,因此讓我們為您的群集創建一個快速的實例。

如下面的gif中看到的那樣,創建數據庫用戶非常簡單。首先,點選Database Access的分頁標籤(位於左側導航欄中的Security底下)。單擊Create new Database User將出現提示,你可以在其中選擇該用戶的身份驗證方法和數據庫用戶特權。

選擇“Password”身份驗證方法,然後為該用戶提供用戶名和密碼。為了方便起見,你甚至可以直接在Atlas中自動生成安全密碼,我強烈建議各位往後可以這樣做。

但是在這個實作中,其實我也是可以隨隨便便的建立一個可以存取和寫入的使用者便可。 DatabaseUser

步驟6: 授予授權的IP地址訪問權限

設置群集的最後一步是選擇允許訪問哪些IP地址。要快速啟動並運行,請將您的群集設置為允許從任何位置進行訪問﹙設定為0.0.0.0/0﹚。 Access

大概在MongoDB Atlas的設定基本上就完成了,而Database﹙數據庫﹚Collection﹙集﹚可以不用設定,因為我們一會兒會利用程式自動生成。



終於要開始回到我們的正題啦!首先我們要先在專案資料夾內打開終端機安裝套件:

npm install --save mongoose

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

建立連線

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

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

const db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  console.log('connected!');
});

而MongoDB Atlas資料庫位址就在Cluster頁面中點撃Connect按鈕,然後選擇Collect your application,接著複製位址到上方程式內。 你看到的位址格式應該是這樣:

mongodb+srv://<數據庫使用者名稱>:<數據庫使用者密碼>@cluster0.<隨機生成專屬碼>.mongodb.net/<數據庫名稱>?retryWrites=true&w=majority

在修改的時候,記得要去掉<>的符號,而數據庫名稱你在這個實作中可以隨便取,另外Collection之後將會以messages作為名稱。

然後試著執行看看,應該會看到一個connected!的訊息,如果沒有的話,檢查一下<數據庫使用者名稱><數據庫使用者密碼>有沒有輸入錯誤。

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

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

module.exports = mongoose.connection;

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



資料模型設計

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 method:

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

this.emit("new_message", msg);

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

再來是get method:

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 method:

// 刪除這段
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);                 // 然後移除
        });
    }
});

好了,現在你不需要擔心資料庫會塞太多歷史垃圾而變得累贅了!



Heroku設定

請不要馬上離開,現在我還有重點的心得!

當各位興高采烈以為已經弄好之後,就一直在locahost玩耍,但卻忘了上載到網上給大家一起用才是當中的重點,而這件事情卻連原作者也沒有提及到。當你申請完帳號而且將專案推送上GitHub之後,其實建立Heroku的專案相當簡單,所以那些教學大家可以自己查找,我也不在這裡長篇大論。

然而,要提醒大家的是我們之前在index.js所設立的port本應是80,但是在要放到Heroku的時候請各位必須把它改回3000,不然在成功deploy之後網站會出現getting "Application Error"的警告。這是因為它的設定是預設在3000的埠。

另外,在推送到GitHub後大家可能會收到警告電郵指專案不安全,皆因大家擺放了含有帳號密碼的MongoDB Atlas資料庫連結在db-connector.js。解決方法也十分簡單,首先把連結的項目改為以下代碼:

const uri = process.env.MONGODB_URI;

然後按照下圖在Heroku的專案設定上加入MONGODB_URI以及連結作為關鍵碼,這樣就可以避免別人在你的程式碼上找到你的數據庫資料了。 Heroku



結語

這一篇也算是系列文的最後一篇了。這三篇文章從什麼都沒有到作出一個擁有記錄功能的聊天室,雖然功能還是很單純但它卻因為簡單而變得輕巧。

希望這幾篇文章能給予你任何對於 Node.js、Socket.io、MongoDB 學習上的一點幫助。

Online Demo:https://simplechat-service.herokuapp.com/

GitHub Source:https://github.com/alvinau0427/SimpleChat

P.S. 此教學原作者為single9,我也是加插一些小細節跟心得,所以先在此感謝他網上的教學

Modified by Alvin Au © 2023