[Node.JS] 打造 todolist api 3 - 完成所有功能
前一篇已經把整個架構寫好,這次就直接照著架構補完所有功能!
💎 GET - 取得待辦清單
在前端撰寫 todolist 的練習時,都會使用一個陣列變數來存放待辦資料,現在搭配後端,就由 Node.js 負責管理這個陣列變數。
GET 方法非常簡單,只要回傳這個存放待辦事項的變數即可。目前的練習中,變數只是存放在伺服器執行環境的記憶體中,並沒有存成實體檔案(資料庫),當伺服器重啟時記憶體釋放,資料就會遺失
1 | // 宣告用來存放待辦事項的陣列 |
段落測試
測試網址:http://127.0.0.1:3000/todo
測試方法:GET(只要順利回傳空陣列就成功了)
💎 POST - 新增一筆待辦
在撰寫這段時,需要先規劃好一筆待辦事項會有哪些欄位,本次練習只放『標題』、『ID』就好,格式設計如下:
1 | { |
- 標題
- 需要接收
req
傳來的資料,使用 node.js 的 req.on 方法來監聽資料接收的事件。 - 封包大小有限,當傳送的資料大時就會被切成好幾個片段,接收端需要在收到所有的片段之後組合起來,才會取得完整可用的資料。
- 要確保資料已經確實接收,分別使用
req.on('data', function)
來處理資料傳送中的行為,req.on('end', function)
處理接收完的行為。 - 資料檢查:格式設計了使用 title 存放標題,如果傳入的資料不是 JSON 格式,或是沒有這個屬性都可能讓伺服器掛掉,所以需要先檢查是不是符合規範。
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
31const server = http.createServer((req, res) => {
// 建立一個變數來接收傳入的資料
let body = '';
// 當有資料傳入就加到 body 變數裡面
req.on('data', chunk => {
body += chunk;
})
...
case 'POST':
// 資料接收完才會執行
req.on('end', () => {
// 取出 body 裡面的 title,如果沒有會得到 undefined
let title = JSON.parse(body)?.title;
// 有 title 就加到陣列裡面,並且回傳全部待辦
if (title) {
todos.push({
title
});
sendResponse(res, 200, {
"status": "true",
"data": todos
})
// 沒 title 回傳錯誤
} else {
sendResponse(res, 400, {
"status": "false"
})
}
})
- 需要接收
- ID
- ID 需要是不會重複的數值,使用 UUID 套件來完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 引入 uuid 模組,請確認有無安裝該套件
const { v4: uuidv4 } = require('uuid');
...
const server = http.createServer((req, res) => {
...
case 'POST':
req.on('end', () => {
...
if (title) {
todos.push({
title,
// 加入 ID
id: uuidv4()
});
...
})
- ID 需要是不會重複的數值,使用 UUID 套件來完成。
- 防錯
- 雖然前面已經針對 title 檢查,還是需要甚防有心人傳入奇怪的資料,可以使用 try…catch 來捕捉問題。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24case 'POST':
req.on('end', () => {
try {
let title = JSON.parse(body)?.title;
if (title) {
todos.push({
title,
'id': uuidv4()
})
sendResponse(res, 200, {
"status": "true",
"data": todos
})
} else {
sendResponse(res, 400, {
"status": "false"
})
}
} catch (error) {
sendResponse(res, 400, {
"status": "false"
})
}
})
- 雖然前面已經針對 title 檢查,還是需要甚防有心人傳入奇怪的資料,可以使用 try…catch 來捕捉問題。
段落測試(需要使用 POSTMAN)
測試網址:http://127.0.0.1:3000/todo
測試方法:POST(需要在 body 加入 JSON 資料)
測試資料:{"title": "test"}
or 各種奇怪資料
💎 DELETE - 刪除全部待辦
刪除全部非常簡單,只要把陣列清空就好,要注意的是陣列和物件都盡量避免賦值(=)操作,以方法操作為主,所以我在一開始宣告 todos 時是使用 const ,這樣就無法使用 todos = []
,而是使用陣列方法 todos.length = 0
來清空陣列。
1 | case 'DELETE': |
段落測試(需要使用 POSTMAN)
測試網址:http://127.0.0.1:3000/todo
測試方法:DELETE
💎 DELETE - 刪除一筆待辦
- 取得傳入的 ID:
前一篇文章有提到,把待辦的 ID 塞到網址後面的格式會是http://網域/路徑/參數
,使用req.url
會得到/路徑/參數
,接著用字串方法split('/')
切割成陣列,再使用pop()
取得最後一筆資料就是參數了。 - 找出 ID 相符的資料:
使用findIndex
方法來找出待辦事項的陣列中有沒有 ID 相符的資料,符合時會取得該筆資料的index
,沒有符合資料時會得到-1
。 - 刪除陣列資料
成功取得指定資料在陣列中的 index 後就可以使用splice(index, 1)
的方法刪除 1 筆資料,第一個參數是起始的索引位置,第二個參數是要往後刪除幾筆資料。 - 架構調整
調整前:確認網址開頭
(/todo/) >確認請求方法
(PATCH or DELETE)
調整後:確認網址開頭
(/todo/) >確認 ID 是否存在
>確認請求方法
(PATCH or DELETE)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...
const server = http.createServer((req, res) => {
...
} else if (req.url.startsWith('/todo/')) {
// 取得請求網址最後一段帶的 ID
let id = req.url.split('/').pop();
// 找出 todos 裡面有沒有符合 id 的項目
let index = todos.findIndex(el => el.id == id);
// 先判斷有沒有指定 ID
if (index > -1) {
if (req.method == 'PATCH') {
...
} else if (req.method == 'DELETE') {
todos.splice(index, 1);
sendResponse(res, 200, {
"status": "true",
"data": todos
});
} else {
sendResponse(res, 405, {
"status": "false"
});
}
} else {
sendResponse(res, 405, {
"status": "false"
});
}
}
...
}
段落測試(需要使用 POSTMAN)
測試網址:http://127.0.0.1:3000/todo/(任一筆id)
(先用 POST 方法建立幾筆資料,才有 ID 可用)
測試方法:DELETE
💎 PATCH - 編輯一筆待辦
終於到了最後一步,也是集大成的一步,『路徑 ID』和『接收資料』都會用上,可以參考 POST
和 DELETE(單筆)
的寫法自行整合在一起,流程大致如下:
- 檢查連結(url)
- 檢查ID (url)
- 檢查方法 (method)
- 確認接收完資料 (req.on(end))
- 偵錯 (try…catch)
- 檢查接收的待辦資料(title)
- 更新待辦資料(title)
- 回傳待辦資料
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...
if (req.method == 'PATCH') {
req.on('end', () => {
try {
let title = JSON.parse(body)?.title;
if (title) {
// 修改對應 ID 的 title
todos[index].title = title;
sendResponse(res, 200, {
"status": "true",
"data": todos
})
} else {
sendResponse(res, 400, {
"status": "false"
})
}
} catch (error) {
sendResponse(res, 405, {
"status": "false"
});
}
})
}
...
段落測試(需要使用 POSTMAN)
測試網址:http://127.0.0.1:3000/todo/(任一筆id)
(先用 POST 方法建立幾筆資料,才有 ID 可用)
測試方法:PATCH
測試資料:{"title": "test"}
or 各種奇怪資料
💎 結語
寫到這邊,總算把所有功能完成,後續還有許多可以調整優化的(回傳詳細的錯誤訊息、模組化…等),就自行發揮吧!