[Node.JS] 打造 todolist api 3 - 完成所有功能

前一篇已經把整個架構寫好,這次就直接照著架構補完所有功能!


💎 GET - 取得待辦清單

在前端撰寫 todolist 的練習時,都會使用一個陣列變數來存放待辦資料,現在搭配後端,就由 Node.js 負責管理這個陣列變數。
GET 方法非常簡單,只要回傳這個存放待辦事項的變數即可。
目前的練習中,變數只是存放在伺服器執行環境的記憶體中,並沒有存成實體檔案(資料庫),當伺服器重啟時記憶體釋放,資料就會遺失

1
2
3
4
5
6
7
8
9
10
11
12
// 宣告用來存放待辦事項的陣列
const todos = [];

const server = http.createServer((req, res) => {
...
case 'GET':
sendResponse(res, 200, {
"status": "true",
// 改成回傳待辦事項的變數
"data": todos
});
break;

段落測試
測試網址http://127.0.0.1:3000/todo
測試方法:GET(只要順利回傳空陣列就成功了)


💎 POST - 新增一筆待辦

在撰寫這段時,需要先規劃好一筆待辦事項會有哪些欄位,本次練習只放『標題』、『ID』就好,格式設計如下:

1
2
3
4
{
"title": "吃飯",
"id": "xxxxxxxxxxx"
}
  • 標題
    • 需要接收 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
      31
      const 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()
      });
      ...
      })
  • 防錯
    • 雖然前面已經針對 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
      24
      case '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"
      })
      }
      })

段落測試(需要使用 POSTMAN)
測試網址http://127.0.0.1:3000/todo
測試方法:POST(需要在 body 加入 JSON 資料)
測試資料{"title": "test"} or 各種奇怪資料


💎 DELETE - 刪除全部待辦

刪除全部非常簡單,只要把陣列清空就好,要注意的是陣列和物件都盡量避免賦值(=)操作,以方法操作為主,所以我在一開始宣告 todos 時是使用 const ,這樣就無法使用 todos = [],而是使用陣列方法 todos.length = 0 來清空陣列。

1
2
3
4
5
6
7
8
case 'DELETE':
// 陣列長度為 0 = 沒資料
todos.length = 0;
sendResponse(res, 200, {
"status": "true",
"data": todos
});
break;

段落測試(需要使用 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』和『接收資料』都會用上,可以參考 POSTDELETE(單筆) 的寫法自行整合在一起,流程大致如下:

  1. 檢查連結(url)
  2. 檢查ID (url)
  3. 檢查方法 (method)
  4. 確認接收完資料 (req.on(end))
  5. 偵錯 (try…catch)
  6. 檢查接收的待辦資料(title)
  7. 更新待辦資料(title)
  8. 回傳待辦資料
    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 各種奇怪資料


💎 結語

寫到這邊,總算把所有功能完成,後續還有許多可以調整優化的(回傳詳細的錯誤訊息、模組化…等),就自行發揮吧!