今天要來準備存放資料的地方 MongoDB 和如何存取裡面的資料。
回憶
前二天我們花了很多時間在講 Prmoie 和 async/await,對於到處都是非同步的 Node.js 來說是很重要的,務必多多練習。
目標
今天要來準備存放資料的地方 MongoDB 和如何存取裡面的資料。
- 安裝 docker
- 安裝並執行 MongoDB
- 使用 MongoDB driver for Node.js
- 使用 mongoose
Docker 架設 MongoDB
因為我希望二周目可以集中心力在講前後端,所以 Docker 的詳細使用會放到三周目,但我還是會保持最底限度的了解。
Docker 它可以安裝在不同的作業系統,它關鍵的技術是 容器(container)。當容器初次建立時會載入某個映象檔(image),映象檔打包著相關程式碼、函式庫、環境配置檔,然後容器就會執行在一個沙箱的執行環境。這技術跟模擬整個作業系統的虛擬機器不同,容器是更小的執行環境。
因為容器是執行在 Docker Engine 上,且我們在三周目也會自己做映象檔並寫執行組態檔,所以我們要安裝 Docer Desktop,它的功能比較多。
用 docker 架設 mongodb
docker 環境
- 依照自己作業系統安裝
- 灌完後執行 docker,就會有個常駐的小圖標
,之後我們就可以用
docker指令了
mongodb 容器執行
-
複製
hello-express專案(或繼續使用)改名成hello-mongo。(若沒有hello-express的人可以看 Day 8 - 一周目- 開始玩轉後端(一)) -
下載 mongodb 的映象檔 (mongodb 官方印像檔 也有其它版本可以下載)
1docker pull mongo:4.1
docker images可以列出有下載的映象檔,看看有沒有下載成功 -
在
hello-mongo根目錄建立一個 data 資料夾mkdir data。用來放 mongodb 的資料,可以讓 mongo 容器刪除時可以留下資料,下次建立容器可以繼續使用 -
terminal 移到專安根目錄後,建立容器且執行 mongo 容器
1docker run --name mongo4 -v $(pwd)/data:/data/db -d -p 27017:27017 --rm mongo:4.1docker ps確認容器有執行
我們用映象檔
mongo:4.1建立了名為mongo4的容器,掛載hello-mongo根目錄下的data資料夾到容器內。容器在背景執行,且對外的 port 號是 27017。當容器停止後自行移除。 -
確認 mongodb 資料庫有運行
1docker exec mongo4 mongo --eval "print(version())"
-
若需要停止容器 (關掉 mongodb 資料庫),請輸入
1docker stop mongo4就會停止容器並刪除
到目前為止,我們完成了用 docker 架設 mongodb 資料庫
若要部署 docker mongo,應該還要加上安全性的設定(像是帳密),且用長駐的 docker container 執行(像是用
docker-compose組態設定)
在 Node.js 中使用 MongoDB
架設完 mongodb 資料庫後,我們就可以用 client 端連入資料庫使用了。
mongodb官方提供很多程式語言的 driver (就是套件)連入資料庫,見 MongoDB Drivers and ODM。我們的 Node.js 是 MongoDB Node.JS Driver。
用 mongo 指令連入資料庫
另外,我強力建議開發時,安裝 shell(cli) 版本的 client,可以方便我們直接下 mongo 指令連線後做練習和測試,但不同的作業系統有自己的安裝方法,請見
Install MongoDB。只是上面的安裝方法不僅安裝 shell 版還安裝伺服器版,也有只安裝 shell client 的方法,但請自己研究一下。
針對有潔癖的人、不想在本機端安裝的人,我提供一個方法使用 mongo 指令,就是:
1進入容器內
因為 mongo 容器中除了資料庫的執行檔還有包含 shell client,所以進入容器就可以使用 mongo 指令,也不用擔心本機端安裝的 shell clinet 版本號不合資料庫版本號。
- 輸入
docker exec -it mongo4 bash,就可以進入容器內
- 就能用
mongo指令,連入資料庫
- 不用的時候直接關 terniaml 或 ctrl + c 和
exit就可以離開容器
mongo指令直接下時是連到本機端 localhost 的 27017 port 的預設值
Node.js Driver 連入
Node.js Driver 提供的非同步的函數,幾乎同時支援 callback 和 回傳 Promise,之後的例子我們儘量使用 Promise
-
安裝 Node.js Driver
1npm install mongodb --save -
修改
./router/index.js,使用mongodb套件,加入一個GET /api/mongoapi 來看 mongodb 是否連線成功注意: 下面的程式,每次打一次 API 都會建立一條連線,很浪費資源,晚一點我們會修改下面的程式碼
1const MongoClient = require('mongodb').MongoClient; 2 3router.get('/api/mongo', function (req, res, next) { 4 // mongodb 位置 5 const url = 'mongodb://localhost:27017'; 6 7 // 資料庫名 8 const dbName = 'myproject'; 9 10 // 連立一個 MongoClient 11 const client = new MongoClient(url, {useNewUrlParser: true}); 12 13 // client 開始連線 14 client.connect() 15 .then(() => { 16 res.json({ 17 isConnected: true, 18 }); 19 }) 20 .catch(error => { 21 console.error(error); 22 res.json({ 23 isConnected: false, 24 }); 25 }); 26}); -
用 Postman 打看看
GET /api/mongo,若有連上 mongodb1{ 2 "isConnected": true 3}若把 mongodb 關掉 (
docker stop mongo4),會得到1{ 2 "isConnected": false 3}
重用(reuse)連線
1const client = new MongoClient(url);
2client.connect();
會建立一條連線,一但接上,沒有手動 client.close() 或發生斷線,此連線不會釋放。因此,我們要留下已連線的 client,放在未來可以拿的到的地方
1const MongoClient = require('mongodb').MongoClient;
2
3// index.js 執行就建立連線
4const url = 'mongodb://localhost:27017';
5const dbName = 'myproject';
6const client = new MongoClient(url, {useNewUrlParser: true});
7client.connect()
8 .then((connectedClient) => {
9 console.log('mongodb is connected');
10 })
11 .catch(error => {
12 console.error(error);
13 });
14
15router.get('/api/mongo', function (req, res, next) {
16 res.json({
17 isConnected: client.isConnected(),
18 });
19});
Collection 的 CRUD (Create, Read, Update, Delete)
上一節我們談了建立連線,現在要來操作資料,先來了解 MongoDb 的名詞對比 MySQL
| type | data collection | element |
|---|---|---|
| MySQL(SQL) | table | record |
| MongoDb(NoSQL) | collection | document |
MongoDB 的 document 有以下特性:
- 物件資料:一個 collection 可放入很多 document,且 document的資料可以是 JSON 或 BSON (Binary JSON)。
- 物件格式:collection 不用預先定義 sechma,也就是 document 想塞什麼物件都可以。
插入資料
修改 POST /api/echo,利用 Immediately-Invoked Function Expressions (IIFE) 立刻執行 async function(),寫入完成後才回應 request
1router.post('/api/echo', function (req, res, next) {
2 const body = req.body;
3
4 // 寫入 MongoDb
5 const worker = (async function (data) {
6 const db = client.db(dbName);
7 const collection = db.collection('echo');
8 const result = await collection.insertOne(data);
9 console.log(result);
10 return result;
11 })(body);
12
13 // 回應
14 worker.then(() => {
15 res.json(body);
16 })
17 .catch(next); // 發生 error 的話,next() 交給之後的 middleware 處理,express 有預設的處理方法
18});
確認資料有寫入
- shell 連入 mongodb
1mongo 127.0.0.1:27017/myproject - 查看
echo內的 document1db.echo.find()
怎麼查文件
可能的操作很多,自己查文件比較有效率,所以說說我怎看文件的,提供剛學習的人一些方向
一進入MongoDB Node.JS Driver,會看到 Reference 和 API
-
Reference:你可能要學習使用,就選這個
- Quick Start/Getting started:大部分的文件都會有這個,給想要立刻使用、體驗,不需要太多的預備知識。沒概念的人或新手就選這個玩一玩。
- Tutorials: 學習一些必要知識的教學
-
API:你已有使用概念,但想要查函數的定義/簽章或看看還有提供什麼函數?
還可以查函數名,輸入
insert,就可以查到Collection類別的inserOne函數定義
例如:我要插入資料時
- 怎麼拿到 collection?
db.collection('echo')回傳 collection 物件,不是 promise - 怎麼拿到 db:
client.db(dbName)回傳 db 物件,不是 promise - 就可以插入資料
1const db = client.db(dbName); 2const collection = db.collection('echo'); 3const result = await collection.insertOne(data);
我寫文章的當下也是在查文件、看用法、看函數的簽章,不是憑空寫的
- 怎麼拿到 collection?
查文件的技巧是要練習的,也會越來越快。若沒概念就找 Quick Start/Getting started/Tutorials,或用 Google 找關鍵字看別人的用法,最後在學習查 API 文件或看官方教學文件。
另外,有個叫 Dash(for mac, iOS)的軟本可以查文件用(和程式碼自動輸入),也是可以用用看。我自己是用不習慣,所以我還是喜歡去官網看文件。
接下來,我們最後介紹 object modeling 套件mongoose 做為結尾。
使用 object modeling 套件:mongoose
有沒有發現 MongoDB Node.JS Driver 的 github 網址是 https://github.com/mongodb/node-mongodb-native? node-mongodb-native 是官方提供操作 mongodb 用的底層套件,它提供的函數很底層,像我們之前的例子:
1const db = client.db(dbName);
2const collection = db.collection('echo');
3const result = await collection.insertOne(data);
要一個個串起來使用。
當後端肥大起來似乎沒這麼方便,為了提供更高層級的思維,就有人寫出 object modeling / ORM(Object-Relational Mapping) / ODM(Object Document Mapper) 這類的套件,甚至不同資料庫、不同的程式語言大多有。
1為了把資料(document/record)當做是物件,當操作物件時,套件會自己使用底層套件與資料互動。
你可以想想以自己寫 SQL 的經驗,ORM套件使你從串SQL文字解脫,變成物件的操作。但也不是這麼美好,因為你又要多學一個套件,不同的 ORM 也可能有不同的用法。
要不要用見人見智(見:Should I Or Should I Not Use ORM ?),我是覺得要用,寫起來比較快,但也要學會用底層套件,因為當你預到效能問題或ORM 套件有bug,才有解套的可能。我的學習策略是先用底層套件 mongodb,等用一陣子才引入 mongoose,你會了解用/不用 mongoose 後的酸甜苦辣。
Quick start
這例子來自mongoose 官方首頁, 直接表明了核心想法
1// 建立連線
2const mongoose = require('mongoose');
3const dbName = 'myproject';
4mongoose.connect(`mongodb://localhost:27017/${dbName}`); // resolve with Connection value
5
6// 定義 "Cat" 的模型(也可以說是Schema),預設的 collection 的名稱會用復數、小寫,所以是 "cats"
7const Cat = mongoose.model('Cat', { name: String });
8
9// 建立物件
10const kitty = new Cat({ name: 'Zildjian' });
11
12// 存入資料庫
13kitty.save().then(() => console.log('meow'));
有幾點要知道一下:
mongoose有一個預設的 connection,mongoose.connect()reolve value 是 Connectino 物件。若需要建立更多的連線可以用mongoose.createConnection()(見:Multiple connections)- CRUD 可能有型如:
xxx(),xxxMany(),xxxOne() find()是回傳的Query,不是資料本身- Query 和 Aggregate 使用時可能會用鍊式語法,最後才用
exec()實際送出查詢
總結
今天我們利用 Docker 架設 MongoDB,還用 Node.js Driver 連入後插入資料。最後介紹 object modeling 套件 mongoose,方便我們對 MongoDB 操作。
評論