Lala Code

Lala 的前端大補帖,歡迎一起鑽研前端技術😊

0%

用 Node.js 打造 CRUD RESTful API

nodeJS

我們將使用 Node.js 建立一個 CRUD(新增、讀取、更新、刪除)的 RESTful API。RESTful API 是現代軟體開發中非常重要的一部分,它提供了一種標準化的方式來訪問和操作資料,並支援各種前端和移動應用。


開發流程

  1. 建立 Server
  2. 設置 cors 表頭資訊
  3. 設置 OPTIONS API
  4. 404 無對應路由
  5. 設置 RESTful API (GET、POST、DELETE、PATCH)


1. 建立 Server

新增一個 app.js,透過 createServer 開啟伺服器
app.js

1
2
3
4
5
6
7
8
9
import http from 'http';

const server = http.createServer((req, res) => {
// TODO...
});

server.listen(3000, () => {
console.log('Server is running on port 3000');
});



2. 設置 cors 表頭資訊

1
2
3
4
5
6
7
8
const server = http.createServer((req, 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' // json 格式
}
});
  • Access-Control-Allow-Headers:允許在實際請求中使用特定的請求標頭
  • Access-Control-Allow-Origin:允許跨來源的來源,*表示任何來源
  • Access-Control-Allow-Methods:允許使用 HTTP Methods
  • Content-Type:回傳的內容格式


3. 設置 OPTIONS API

Preflight(預檢)機制

由於網頁與主機的 domain 不同,在某些時機點,call API 時會傳送兩次 request

第一次造訪雲端,Request Method 會是 OPTIONS,確認主機可以造訪

第二次才會是使用者發出的請求(Delete)

Preflight*圖片來自六角學院

如果滿足以下任一項條件時會發出預檢請求

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

設定 OPTIONS 條件

1
2
3
4
5
6
7
8
9
10
11
12
13
const server = http.createServer((req, 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' // json 格式
}

if(req.method === 'OPTIONS') {
res.writeHead(200, headers);
res.end();
}
});



4. 404 無對應路由

1
2
3
4
5
6
7
8
9
//...OPTIONS
} else {
res.writeHead(404, headers);
res.write(JSON.stringify({
"status": "false",
"message": "無此頁面"
}));
res.end();
}



5. 設置 RESTful API

GET

設置路由與 http method 後,進行回傳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import http from 'http';
const todos = [];

const server = http.createServer((req, res) => {
const headers = ...

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

確認請求的路由如果為 ‘/todos’ 且為 GET 時,將會回傳 status code 200,並回傳 JSON 資訊

todos 會是所有資料的集合,目前為空陣列

在 Postman 測試看一下,就會出現我們設定的資訊

node get

POST

在 Node.js 中,當進行 HTTP POST 請求時,資料通常是以 TCP 封包的形式進行傳輸的。當使用者在前端透過瀏覽器提交表單或發送 POST 請求時,資料會被分割成多個小塊(chunks),並以 TCP 封包的方式傳送到伺服器。

當伺服器接收到這些封包時,它並不會立即得到完整的資料。相反,它會持續累加這些封包,直到收到所有封包並組合成完整的資料。這是因為 TCP 封包的傳輸順序不一定與原始資料的順序完全一致,有可能先收到後面的封包再收到前面的封包,因此伺服器需要等待所有封包到達,然後根據每個封包的順序來組合資料,才能得到最終的完整資料。


node post
*圖片來自六角學院


在資料尚未完整組合之前,伺服器通常會將每個封包的內容暫時儲存起來,這種暫存區域稱為緩衝區(buffer)。當所有封包都到達並組合完成後,伺服器會將緩衝區中的資料取出並進行處理,以獲得最終的完整資料。

所以我們會用一個 body 來將 chunk 組起來

1
2
3
4
5
6
7
8
let body = '';
req.on('data', chunk => {
body += chunk;
})

req.on('end', () => {
console.log(JSON.parse(body).title);
})

在註冊資料時,用戶提交的表單或 POST 請求中的資料可能會非常龐大,可能會被分割成數個小塊封包。伺服器在接收到這些封包後,會持續累加這些封包,直到收到所有封包並組合成完整的註冊資料。當伺服器確定資料已完整收到時,它就會將這些資料進行處理,例如儲存到資料庫中或進行其他相關處理。

這種方式可以確保資料在傳輸過程中不會丟失或損毀,同時也讓伺服器能夠在收到完整資料後再進行相應的處理,以確保資料的完整性和正確性。

app.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import http from 'http';
import { v4 as uuidv4 } from 'uuid';
import { errHandle } from "./errorHandle.js";
const todos = [];

const server = http.createServer((req, res) => {
// headers...

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

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

server.listen(3000, () => {
console.log('Server is running on port 3000');
});

當使用者傳入 title,如果 title 為有效值,將會回傳成功訊息,並回傳唯一性的 id,如錯誤將會執行錯誤處理

為了檔案整潔,另外抽了一支 errorHandle.js 專門處理錯誤訊息

errorHandle.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const errHandle= (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', // 允許使用Methods
'Content-Type': 'application/json' // json 格式
}
res.writeHead(400, headers);
res.write(JSON.stringify({
"status": "false",
"message": "欄位未填寫正確,或無此 to id"
}));
res.end();
}

用 Postman 測試,成功將會出現回傳資訊

node post

輸入無效值,將會出現 400 錯誤訊息

node post

DELETE

全部清空

目前 GET /todos 有一筆我們剛剛新增的資料,我們現在要把資料全部清空,作法也很簡單

node get

app.js

1
2
3
4
5
6
7
8
9
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 集合全部清空,並回傳成功訊息

node delete

清空單筆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else if(req.url.startsWith('/todos/') && req.method === 'DELETE') {
const id = req.url.split('/').pop(); // 取得最後一筆資料
const index = todos.findIndex(item => item.id === id);
if(index !== -1) {
todos.splice(index, 1);
res.writeHead(200, headers);
res.write(JSON.stringify({
"status": "success",
"data": todos,
}));
res.end();
} else {
errHandle(res);
}
}
  • startsWith() 用於檢查一個字串是否以指定的字串開頭。這個方法會回傳一個布林值,表示該字串是否以指定的字串開頭
  • 找到符合路由的 id 並將該筆資料刪除

將會移除符合 f061bfb2-64da-4949-8230-cd07c3e7137d 此組 id 的資料

node delete

PATCH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
else if(req.url.startsWith('/todos/') && req.method === 'PATCH') {
req.on('end', () => {
try {
let title = JSON.parse(body).title;
const id = req.url.split('/').pop();
const index = todos.findIndex(item => item.id === id);
if(title !== undefined && index !== -1) {
todos[index].title = title;
res.writeHead(200, headers);
res.write(JSON.stringify({
"status": "success",
"data": todos,
}));
res.end();
} else {
errHandle(res);
}
} catch (error) {
errHandle(res);
}
})
}
  • 找到符合路由的 id
  • 如有符合 id 的資料且 title 的資料為有效就進行修改

node patch



🚀實體工作坊分享

玩轉 Web頁面的前端技術(HTML/CSS/JS) 一日體驗課

最近時賦學苑開了實體體驗課,即使你對程式碼沒有概念也能上手!Lala 會帶你一起做出一個個人品牌形象網站,帶你快速了解前端的開發流程,快跟我們一起玩轉 Web 吧!



🚀線上課程分享

線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。

Hahow

Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住



六角學院

如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!


Udemy

Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。
❤️