Day11 - Express 與 Mongoose

Express 與 Mongoose

前言

在上一篇文章中,我們初步建立了 MongoDB Atlas 的帳號,並且快速認識了 Mongoose,接下來當然就是要來整合再一起啦~

Express 與 Mongoose

首先一開始的一些起手式我就不再次說明了,直接附上指令讓你可以快速建立一個專案:

1
mkdir example-express-mongoose
1
cd example-express-mongoose
1
npm init -y
1
npm i express mongoose
1
touch index.js

理論上來說,上面的指令你應該沒有任何一行看不懂的,如果有,那就代表你沒有認真看前面的文章,建議你可以先回去看看~

被抓包

那麼我們這次要寫什麼呢?其實很簡單,把前面寫的範例程式碼把它整合到一起就好了,所以我們先來看看前面的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const express = require('express');
const app = express();

const data = [];


app.use(express.json());

app.get('/todos', (req, res) => {
res.send(data);
});

app.post('/todos', (req, res) => {
const { title, completed } = req.body;

data.push({
id: new Date().getTime(),
title: cacheBody.title,
completed: cacheBody.completed,
});

res.send(data);
});

app.listen(3000)

接下來,補上我們前一篇所使用的 Mongoose 的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express');
const mongoose = require('mongoose');

const app = express();

// ...略過其他程式碼

mongoose.connect('mongodb+srv://example:[email protected]/?retryWrites=true&w=majority')
.then(() => console.log('Connected to MongoDB...'))

// ...略過其他程式碼

app.listen(3000)

當你在終端機輸入 npm start or node index.js 後,你會看到 Mongoose 已經幫你連上 MongoDB Atlas 了囉~

解決 Mongoose 連接問題

這時候有些人應該會有個好奇的疑問點…

「如果我在 Mongoose 連接成功之前,就已經有人來存取我的 API 了,那不是會出錯嗎?」

這個問題問得很好,確實是有這種可能性會發生,如果我們的專案在成功連接之前,就先接受了 API 請求,而剛好這些 API 請求需要操控資料庫的話,那麼確實是會出錯沒有錯。

那麼我們該怎麼解決這個麻煩的問題呢?其實我們只需要善加利用前面所學的 Middleware 就可以了,我們可以在 Middleware 中判斷是否已經連接成功,如果沒有的話,就回傳一個錯誤訊息,如果有的話,就繼續執行下一個 Middleware。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const express = require('express');
const mongoose = require('mongoose');
const app = express();

async function connectMongoDB () {
try {
await mongoose.connect('mongodb+srv://example:[email protected]/?retryWrites=true&w=majority')
console.log('Connected to MongoDB...')
connectStatus = true;
} catch (error) {
console.log(error)
}
}

connectMongoDB()

// ...略過其他程式碼

app.use(express.json());

app.use((req, res, next) => {
if (connectStatus) {
next();
} else {
res.status(503).send({
status: false,
message: 'Server is not ready'
});
}
})

// ...略過其他程式碼

app.listen(3000)

這樣子我們就可以避免在 Mongoose 還沒跟 MongoDB Atlas 連接成功之前,就先接受 API 請求的問題囉!

定義 Schema

在準備跟 MongoDB 互動(ex:CRUD)之前,我們必須先定義 Schema,這樣子才能夠讓 Mongoose 知道我們要存取的資料長什麼樣子。

1
2
3
4
5
const todoSchema = new mongoose.Schema({
id: Number,
title: String,
completed: Boolean,
});

Note
CRUD 意指 Create、Read、Update、Delete,也就是我們常說的「增刪改查」。

那麼什麼是 Schema 呢?Schema 是用來定義 MongoDB 結構的一種概念,主要說明了這些資料的型別、欄位名稱、預設值等等,這樣子我們才能夠在存取資料時,知道該怎麼去存取。

因此以剛剛的範例程式碼來講,我們定義了一個 todoSchema,裡面有兩個欄位,分別是 titlecompleted,而這兩個欄位的型別分別是 String 跟 Boolean。

那麼只要是這些 Schema 屬性之外的資料,都會被忽略,也就是說,如果我們在存取資料時,傳入了一個多餘的欄位,那麼這個欄位就會被忽略,不會被存到資料庫中。

透過 Schema 可以確保我們資料的一致性和完整性。

定義 Model

定義完 Schema 之後,接著就是要來建立 Model,通常 Modal 會對應特定的 Schema,而且每個 Model 都會跟資料庫中的一個 collection 對應,也就是說,我們可以透過 Model 來存取資料庫中的資料。

1
2
3
4
5
6
7
const todoSchema = new mongoose.Schema({
id: Number,
title: String,
completed: Boolean,
});

const Todo = mongoose.model('Todo', todoSchema);

你也可以把 mongoose.model 當作 new 實例化的概念沒有錯,因為它們的用法很像,只是 mongoose.model 是用來建立 Model 的,而 new 則是用來建立實例的。

透過這個 Modal 我們就可以針對資料庫做 CRUD(增刪改查)的動作囉!

存取資料庫與寫入資料

那麼目前我們完整程式碼長怎樣呢?我們先來看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
const express = require('express');
const mongoose = require('mongoose');
const app = express();


let connectStatus = false;

async function connectMongoDB () {
try {
await mongoose.connect('mongodb+srv://example:[email protected]/?retryWrites=true&w=majority')
console.log('Connected to MongoDB...')
connectStatus = true;
} catch (error) {
console.log(error)
}
}

connectMongoDB()

const data = [];

app.use(express.json());

app.use((req, res, next) => {
if (connectStatus) {
next();
} else {
res.status(503).send({
status: false,
message: 'Server is not ready'
});
}
})

const todoSchema = new mongoose.Schema({
id: Number,
title: String,
completed: Boolean,
});

const Todo = mongoose.model('Todo', todoSchema);

app.get('/todos', (req, res) => {
res.send(data);
});

app.post('/todos', (req, res) => {
const { title, completed } = req.body;

data.push({
id: new Date().getTime(),
title: cacheBody.title,
completed: cacheBody.completed,
});

res.send(data);
});

app.listen(3000)

我們第一個要做的事情是先來做 Post 的動作,也就是寫入資料庫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express');

// ...略過其他程式碼

app.post('/todos', async (req, res) => {
const { title, completed } = req.body;

const todo = new Todo({
id: new Date().getTime(),
title,
completed,
});

await todo.save();
res.send({
status: true,
message: 'Create todo successfully',
});
});

// ...略過其他程式碼

app.listen(3000)

其實我們可以發現實作難度真的不高,只要透過 new 來建立一個實例,然後透過 save 來存入資料庫就可以了。

取得 Todo 資料的話更簡單了,只需要透過 find 就可以取得資料庫中的資料囉!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const express = require('express');

// ...略過其他程式碼

app.get('/todos', async (req, res) => {
const todos = await Todo.find();
res.send({
status: true,
data: todos,
});
});

// ...略過其他程式碼

app.listen(3000)

基本上以上程式碼你就可以透過 Postman 來測試了,不意外的話,當你打完 Post /todos 之後,你會發現資料庫中多了一筆資料,接著你在打 Get /todos 之後,你會得到以下資料

1
2
3
4
5
6
7
8
9
10
11
12
{
"status": true,
"data": [
{
"_id": "64e08a11f5e1d20be8115fb0",
"id": 1692437009750,
"title": "Hello",
"completed": false,
"__v": 0
}
]
}

恭喜你,基本的新增與讀取的功能完成囉!

剩下的編輯與刪除的部分,我也想保留給你作為小功課,你可以試著自己實作看看,這邊我也補充相關資源讓你可以參考

以上資源我相信可以幫助你做出來的。

那麼這邊你應該會好奇 __v_id 這兩個屬性是什麼東西,這邊我也做一下補充。

這兩個屬性是 MongoDB 預設的屬性,_id 是 MongoDB 的主鍵(key),而 __v 則是版本鍵(version key),比較需要注意的是 _id 是 ObjectId,而不是我們一般的數字。

如果你希望不要有這兩個屬性,你可以透過 select 來過濾掉

1
const todos = await Todo.find().select('-__v -_id');

那麼這一篇差不多就先到這些結束,我們下一篇見。

碎碎念

終於來到第十一天了,其實我發現我身上的存稿真的不是很夠 Orz
而且我現在人正在日本發文唷~