[Node] Node todolist API
完整程式碼: https://github.com/Stevetanus/To-do-list/tree/master/to-do-list%20(Node)
基本寫法
這是一個包含 GET 、 POST 、 DELETE 、 PATCH 請求,以及有錯誤處理和 CORS 處理 headers 的 API 伺服器。
建立 server
透過 http 模組,建立在 localhost://3005 的 server,requestListener
函式去處理 req
、res
的回應,最基本的伺服器要帶入 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 發送 /todos
的 GET
請求,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 物件會有 title
和 id
屬性 (uuid)。try catch
處理 body
的資料不當或是空的情況,交給 errorHandle.js
。 JSON.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 刪除
刪除全部 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();
刪除特定 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 去做替代,若是沒有 title
、 title
為空、 找不到該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 會請求兩次的原因,PATCH 和 DELETE 都會觸發。
} else if (req.method == "OPTIONS") {
// preflight
res.writeHead(200, headers);
res.end();