Skip to main content

[Node] Node todolist API

完整程式碼: https://github.com/Stevetanus/To-do-list/tree/master/to-do-list%20(Node)

基本寫法

這是一個包含 GETPOSTDELETEPATCH 請求,以及有錯誤處理CORS 處理 headers 的 API 伺服器。

建立 server

透過 http 模組,建立在 localhost://3005 的 server,requestListener 函式去處理 reqres 的回應,最基本的伺服器要帶入 headers ,裡面的 Content-Type 表示傳遞資料的類型

const http = require("http");
const todos = []; // todos 儲存之 array

const requestListener = (req, res) => {
// 不同的 domain 會先發一個 preflight 預檢請求 (OPTIONS API 檢查機制)
const headers = {
"Access-Control-Allow-Headers":
"Content-Type, Authorization, Content-Length, X-Requested-With",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "PATCH, POST, GET, OPTIONS, DELETE",
"Content-Type": "application/json",
};

// routes...
};

const server = http.createServer(requestListener); // 帶入處理 req 和 res 的函式
server.listen(3005);

GET 取得

在 client 發送 /todosGET 請求,JSON.stringify 將 javascript value 轉成 JSON 格式,res.write() 傳回 JSON 檔案,最後要加上 res.end() 表示結束。

  if (req.url == "/todos" && req.method == "GET") {
res.writeHead(200, headers);
res.write(
JSON.stringify({
status: "success",
data: todos,
})
);
res.end();

POST 新增

由於 req 夾帶的資料可能會很多,我們建立 body 去儲存全部的 chunk, todo 物件會有 titleid 屬性 (uuid)。try catch 處理 body 的資料不當或是空的情況,交給 errorHandle.jsJSON.parse() 解析 JSON 為 javascript value。新增成功的話,res.write() 回傳新增成功的 JSON。


const errorHandle = require("./errorHandle");
const { v4: uuidv4 } = require("uuid");

// ...

let body = "";
req.on("data", (chunk) => {
body += chunk;
});

else if (req.url == "/todos" && req.method == "POST") {
req.on("end", () => {
try {
const title = JSON.parse(body).title;
if (title !== undefined) {
const todo = {
title: title,
id: uuidv4(),
};
todos.push(todo);
res.writeHead(200, headers);
res.write(
JSON.stringify({
status: "success",
data: todo,
})
);
res.end();
} else {
errorHandle(res);
}
} catch (error) {
errorHandle(res);
}
});

errorHandle 錯誤處理

回傳代號 400,與錯誤訊息的 JSON,透過 module.exports 匯出給 server.js 使用。

function errorHandle(res) {
const headers = {
"Access-Control-Allow-Headers":
"Content-Type, Authorization, Content-Length, X-Requested-With",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "PATCH, POST, GET, OPTIONS, DELETE",
"Content-Type": "application/json",
};
res.writeHead(400, headers);
res.write(
JSON.stringify({
status: "failure",
message: "欄位未填寫正確,或無此 todo id",
})
);
res.end(); // 關門
}

module.exports = errorHandle;

DELETE 刪除

  1. 刪除全部 todos

    todos.length = 0 將一個 array 的長度歸零,會使該 array 變成空的。

  } else if (req.url == "/todos" && req.method == "DELETE") {
todos.length = 0;
res.writeHead(200, headers);
res.write(
JSON.stringify({
status: "success",
data: todos,
})
);
res.end();
  1. 刪除特定 todos

    req.url.startsWith("/todos/") 確定請求網址可能帶有 id ,.split("/").pop 取得以 / 分開的最後一個字串 (id),透過 findIndex 去找到在 todos 裡面相同 id 的索引,若是回傳 -1 ,就進錯誤處理;找到的話,透過 todos.splice(index, 1) 刪除該筆資料。

  } else if (req.url.startsWith("/todos/") && req.method == "DELETE") {
const id = req.url.split("/").pop();
const index = todos.findIndex((element) => element.id == id);
if (index !== -1) {
todos.splice(index, 1);
res.writeHead(200, headers);
res.write(
JSON.stringify({
status: "success",
data: todos,
})
);
res.end();
} else {
errorHandle(res);
}

PATCH 修改

與上面刪除特定 todos的做法類似,只是將該筆資料用新的 todo 去做替代,若是沒有 titletitle 為空、 找不到該id,進錯誤處理。

  } else if (req.url.startsWith("/todos/") && req.method == "PATCH") {
req.on("end", () => {
try {
const todo = JSON.parse(body).title; // 取得新資料的 title
const id = req.url.split("/").pop();
const index = todos.findIndex((element) => element.id == id);
if (todo !== undefined && index !== -1) {
todos[index].title = todo; // 更換
res.writeHead(200, headers);
res.write(
JSON.stringify({
status: "success",
data: todos,
})
);
res.end();
} else {
errorHandle(res);
}
} catch (error) {
errorHandle(res);
}
});

OPTIONS 預檢

不同於「簡單請求」的例子,「預檢(preflighted)」請求會先以 HTTP 的 OPTIONS 方法送出請求到另一個網域,確認後續實際(actual)請求是否可安全送出,由於跨站請求可能會攜帶使用者資料,所以要先進行預檢請求。

在資料會做修改的跨站請求之中,會先發送一個 OPTIONS 的預檢請求,預檢一次來確定該 server 有符合條件的 headers ,也就是為什麼同一個 API 會請求兩次的原因,PATCHDELETE 都會觸發。

  } else if (req.method == "OPTIONS") {
// preflight
res.writeHead(200, headers);
res.end();

不只是簡單的跨域請求 Preflight Request

MDN 預檢請求