iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
佛心分享-IT 人自學之術

出發吧!後端菜鳥:30 天的後端學習紀錄系列 第 5

Day 05|從原生 Node.js 到 Express:不用再自己處理每個細節

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260918/20169497Si5cQc2nUE.jpg

前言

上一篇我們使用 Node.js 內建的 http 模組,成功建立了第一支 API。當前端送出 GET /todos 時,後端會判斷 Request 的網址與 Method,再回傳 JSON 資料。這代表即使沒有使用任何框架,只靠 Node.js 本身,其實就已經可以建立 HTTP Server 和 API。

但如果繼續往下寫,很快就會發現一個問題:隨著 API 數量增加,需要自己處理的事情也會越來越多。目前只有一個 /todos 還看不太出來,但未來 Todo API 可能會慢慢增加,例如:

GET /todos
POST /todos
PATCH /todos/:id
DELETE /todos/:id

除此之外,可能還會有會員註冊、登入或取得會員資料等 API。如果全部都使用原生 Node.js 判斷 req.urlreq.method,程式碼很快就會出現大量重複的條件判斷,因此這一篇要來認識 Node.js 後端開發中很常見的 Web Framework:Express。

原生 HTTP Server 遇到什麼問題?

先回頭看看 Day 04 建立的 Server:

const http = require("http");

const server = http.createServer(function (req, res) {
  console.log(req.method, req.url);

  if (req.url === "/todos" && req.method === "GET") {
    res.writeHead(200, {
      "Content-Type": "application/json"
    });

    res.end(
      JSON.stringify({
        message: "Hello API!"
      })
    );

    return;
  }

  res.writeHead(404, {
    "Content-Type": "application/json"
  });

  res.end(
    JSON.stringify({
      message: "Not Found"
    })
  );
});

server.listen(3030, function () {
  console.log("Server running at http://localhost:3030");
});

這段程式本身沒有問題,而且也確實完成了一支 API,只是很多事情都需要我們自己處理。收到 Request 之後,要透過 req.url 判斷使用者請求的網址,再透過 req.method 判斷 HTTP Method;準備回傳 JSON 時,還需要自己設定 Content-Type,使用 JSON.stringify() 將 JavaScript 物件轉成 JSON 字串,最後再透過 res.end() 把資料送出並結束這次 Response。

只有一支 API 時,這些工作還不算太麻煩,但如果未來 API 越來越多,就可能開始出現大量類似的條件判斷:

if (req.url === "/todos" && req.method === "GET") {
  // ...
}

if (req.url === "/todos" && req.method === "POST") {
  // ...
}

if (req.url === "/users" && req.method === "GET") {
  // ...
}

if (req.url === "/login" && req.method === "POST") {
  // ...
}

隨著功能增加,我們除了要判斷 URL 和 Method,之後還需要處理 Request Body、網址參數、錯誤處理等問題。也就是說,Node.js 原生的 http 模組本身確實可以建立 Server 和 API,只是 Web 開發中許多常見的工作都需要自己處理,而 Express 就是用來簡化這些工作的框架之一。

Express 解決了什麼?

Express 是建立在 Node.js 之上的 Web Framework,可以先把它理解成:Express 把路由、Request、Response 等 Web 開發中經常使用的操作整理成一套比較方便的 API,讓我們不需要每次都從原生 http 模組開始處理這些細節。

例如原本使用 Node.js 時,我們可能需要自己判斷網址與 Method:

if (req.url === "/todos" && req.method === "GET") {
  // ...
}

使用 Express 之後,可以改寫成:

app.get("/todos", function (req, res) {
  // ...
});

app.post("/todos", function (req, res) {
  // ...
});

app.delete("/todos/:id", function (req, res) {
  // ...
});

從程式碼本身就能直接看出,每一段程式負責的是哪一種 HTTP Method 和哪一個網址。Express 並不是讓 Node.js 突然擁有建立 API 的能力,因為上一篇已經證明 Node.js 本身就能做到,比較準確地說,Express 是把 Web Server 開發中經常出現的操作包裝成更方便使用的方式,讓程式碼更容易撰寫、閱讀與維護。

安裝 Express

和上一篇使用的 http 不同,Express 並不是 Node.js 的內建模組,因此需要另外安裝。

  1. 首先建立專案資料夾並進入資料夾,可以在終端機依序執行 mkdir express-democd express-demo
  2. 接著執行 npm init -y 建立 package.json
  3. 最後使用 npm install express 安裝 Express。
  4. 安裝完成後,就可以建立 server.js,開始撰寫第一個 Express Server。

建立第一個 Express Server

首先使用 require("express") 引入 Express,再透過 express() 建立一個 Express 應用程式,最後使用 app.listen() 啟動 Server:

const express = require("express");

const app = express();

app.listen(3030, function () {
  console.log("Server running at http://localhost:3030");
});

接著在終端機執行 node server.js 啟動 Server,如果終端機出現 Server running at http://localhost:3030,就代表 Express Server 已經成功啟動。

和上一篇的原生 Node.js 寫法相比,原本需要自己使用 http.createServer() 建立 HTTP Server,現在則可以建立 Express 的 app,再透過 app.listen() 啟動服務。底層仍然建立在 Node.js 的 HTTP Server 之上,只是 Express 幫我們包裝了一部分操作,因此開發時不需要每次都自己處理 http.createServer()

app.get() 是什麼?

目前雖然 Server 已經啟動,但還沒有建立任何 API,因此接下來可以加入一個 /todos Route:

const express = require("express");

const app = express();

app.get("/todos", function (req, res) {
  res.send("Hello API!");
});

app.listen(3030, function () {
  console.log("Server running at http://localhost:3030");
});

現在進入 http://localhost:3030/todos,就會在畫面上看到 Hello API!

app.get() 的第一個參數 "/todos" 代表網址路徑,第二個參數則是一個 function,代表當 Request 符合這個 Route 時,要執行什麼事情。因此:

app.get("/todos", function (req, res) {
  res.send("Hello API!");
});

可以理解成「當 Server 收到一個 GET /todos Request 時,就執行這個 function」。這種把不同 HTTP Method 和網址對應到不同處理方式的概念,通常稱為 Routing,也就是路由。

Express 中的 reqres

上一篇使用原生 Node.js 時,其實已經看過 reqres,在 Express 裡也會繼續使用這兩個物件。req 代表 Request,也就是 Client 傳進 Server 的請求;res 則代表 Response,也就是 Server 準備回傳給 Client 的內容。

例如可以透過 req.method 取得 Request 使用的 HTTP Method,也可以透過 req.url 查看這次 Request 的網址:

app.get("/todos", function (req, res) {
  console.log(req.method);
  console.log(req.url);

  res.send("Hello");
});

當我們進入 GET /todos 時,終端機可能會看到 GET/todos。之後建立更完整的 API 時,我們還會透過 req.paramsreq.queryreq.body 取得 Client 傳來的不同資料,它們分別會用來處理網址參數、Query String 和 Request Body,不過目前先知道 req 是用來取得 Request 資訊即可,之後實際用到時再逐一介紹。

res 可以怎麼回傳資料?

res 代表 Response,也就是 Server 準備回傳給 Client 的內容。在 Express 中,如果只是要回傳一般文字,可以使用 res.send("Hello");如果要回傳 API 常見的 JSON 資料,則可以使用:

res.json({
  message: "Hello API!"
});

和原生 Node.js 相比,這裡少了不少原本需要自己處理的步驟。例如使用原生 http 模組回傳 JSON 時,我們可能會這樣寫:

res.writeHead(200, {
  "Content-Type": "application/json"
});

res.end(
  JSON.stringify({
    message: "Hello API!"
  })
);

res.writeHead() 可以一次設定 HTTP Status Code 和 Response Header,因此這裡的 200 代表回傳成功,而 Content-Type: application/json 則是在告訴 Client:「這次 Response Body 裡的資料是 JSON 格式。」接著還需要使用 JSON.stringify() 將 JavaScript 物件轉成 JSON 字串,最後再透過 res.end() 把資料送出並結束 Response。

到了 Express,可以直接改成:

res.status(200).json({
  message: "Hello API!"
});

其中 res.status(200) 負責設定 HTTP Status Code,而 res.json() 則會幫我們處理 JSON Response 所需的其他工作。當我們把一個 JavaScript 物件傳進 res.json() 時,Express 會先將資料轉成 JSON,並設定適合 JSON Response 的 Content-Type,再把資料寫入 Response Body 並送出。

因此原本需要自己處理的:

JSON.stringify({
  message: "Hello API!"
});

會由 res.json() 幫我們完成,而原本需要自己設定的 JSON Content-Type,Express 也會替我們處理。

所以原生 Node.js:

res.writeHead(200, {
  "Content-Type": "application/json"
});

res.end(
  JSON.stringify({
    message: "Hello API!"
  })
);

到了 Express 可以寫成:

res.status(200).json({
  message: "Hello API!"
});

兩段程式完成的事情很接近,只是 Express 把常用操作包裝成更容易使用的方法。

為什麼 Express 不用寫 res.end()

使用 Node.js 原生的 HTTP Server 時,一次 Response 最後需要被結束,而 res.end() 的作用就是告訴 Server:「這次 Response 已經完成,可以結束了。」

上一篇我們會看到這樣的寫法:

res.end(
  JSON.stringify({
    message: "Hello API!"
  })
);

這裡的 res.end() 不只可以表示 Response 結束,也可以直接帶入最後要送出的資料,所以這段程式可以理解成:先使用 JSON.stringify() 把 JavaScript 物件轉成 JSON 字串,再將資料送出去,同時結束這次 Response。

到了 Express,我們則會寫成:

res.json({
  message: "Hello API!"
});

這並不是因為 Express 不需要結束 Response,而是 res.json() 已經幫我們處理了。前面提到,res.json() 會將 JavaScript 資料轉成 JSON、設定適合的 Content-Type、把資料送出,最後也會結束這次 Response,因此不需要再另外呼叫 res.end()

同樣地,res.send("Hello") 也會把資料送出並結束 Response。所以比較準確的理解不是「Express 不需要 res.end()」,而是 Express 提供的 res.json()res.send() 等方法已經幫我們完成送出資料與結束 Response 的工作。

因此如果把原生 Node.js 和 Express 對照起來,可以先這樣理解:

原生 Node.js                    Express

writeHead(200, ...)      →      res.status(200)
設定 Content-Type        →      res.json() 處理
JSON.stringify()         →      res.json() 處理
res.end()                →      res.json() 處理

這也是為什麼 Express 的寫法看起來比原生 Node.js 簡潔許多,並不是這些工作消失了,而是 Express 幫我們把它們包裝起來。

res.send()res.json() 有什麼差別?

目前可以先簡單理解成:如果只是要回傳一般文字,可以使用 res.send();如果是建立 API,要回傳 JSON 格式的資料,則通常會使用 res.json()

例如一般文字可以寫成 res.send("Hello API!"),如果要回傳 Todo 資料,則可以寫成:

res.json({
  data: [
    {
      id: 1,
      title: "Learn Express"
    }
  ]
});

因為接下來主要是在建立 RESTful API,所以文章中會比較常看到 res.json()

回傳 Status Code

上一篇也介紹過 HTTP Status Code,例如 200 OK201 Created400 Bad Request404 Not Found500 Internal Server Error。在 Express 中,可以透過 res.status() 設定 Status Code,再接著使用 res.json() 回傳 JSON:

res.status(200).json({
  message: "Success"
});

如果是找不到資源,也可以寫成:

res.status(404).json({
  message: "Not Found"
});

這種寫法可以理解成先透過 res.status(404) 設定 Status Code,再透過 .json() 將 JSON 資料送出並結束 Response。Express 很常使用這種連續呼叫方法的寫法,因此之後在 API 中會經常看到。

把 Day 04 API 改寫成 Express

現在就可以把上一篇使用原生 Node.js 建立的 API 改寫成 Express。上一篇的版本如下:

const http = require("http");

const server = http.createServer(function (req, res) {
  console.log(req.method, req.url);

  if (req.url === "/todos" && req.method === "GET") {
    res.writeHead(200, {
      "Content-Type": "application/json"
    });

    res.end(
      JSON.stringify({
        message: "Hello API!"
      })
    );

    return;
  }

  res.writeHead(404, {
    "Content-Type": "application/json"
  });

  res.end(
    JSON.stringify({
      message: "Not Found"
    })
  );
});

server.listen(3030, function () {
  console.log("Server running at http://localhost:3030");
});

改成 Express 之後:

const express = require("express");

const app = express();

app.get("/todos", function (req, res) {
  res.status(200).json({
    message: "Hello API!"
  });
});

app.use(function (req, res) {
  res.status(404).json({
    message: "Not Found"
  });
});

app.listen(3030, function () {
  console.log("Server running at http://localhost:3030");
});

兩個版本完成的事情其實差不多。當 Client 發送 GET /todos 時,Server 都會回傳 { "message": "Hello API!" };如果輸入不存在的網址,例如 GET /abc,則會回傳 404,以及 { "message": "Not Found" }

這裡第一次出現了 app.use()。目前可以先把它理解成:如果 Request 沒有被前面的 Route 處理,就會繼續往下執行,因此把這段程式放在所有 Route 的最後面,就可以暫時作為找不到網址時的 404 處理。

app.get("/todos", function (req, res) {
  res.status(200).json({
    message: "Hello API!"
  });
});

app.use(function (req, res) {
  res.status(404).json({
    message: "Not Found"
  });
});

實際上 app.use() 和 Express 的 Middleware 機制有很大的關係,之後會再另外介紹。現在只需要先知道,Express 會依照程式碼的順序檢查 Route 和 Middleware,因此這個 404 處理要放在其他 Route 後面。

為什麼還要先學原生 Node.js?

既然 Express 比較方便,可能會產生另一個疑問:既然最後都會使用 Express,那之前為什麼還要先用原生 Node.js 建立 Server?

原因是如果直接從 Express 開始,很容易只記住 app.get()res.json()app.listen() 這些語法,卻不知道背後到底發生了什麼事情。

但我們現在已經知道,Client 發送 Request 後,Server 會取得 Request 的 Method 和 URL,再根據這些資訊決定要執行哪些程式,最後建立 Response 並回傳給 Client。因此現在看到:

app.get("/todos", function (req, res) {
  res.json({
    message: "Hello"
  });
});

就不只是單純記住 Express 的寫法,而是可以理解 app.get("/todos") 正在處理 GET /todos Request,而 res.json() 則是在建立 JSON Response,包含資料轉換、設定對應的 Response Header、送出資料以及結束這次回應。

所以學習原生 Node.js 的目的,不是以後每一個專案都要自己用 http.createServer() 建立 Server,而是先理解 Web Server 和 HTTP Request / Response 的基本運作方式。之後再使用 Express,就比較不容易只停留在記憶語法,而能理解框架替我們處理了哪些事情。

今日成果

今天我們正式把上一篇的原生 Node.js API 改寫成 Express,並理解 Express 和原生 Node.js 之間的關係。雖然 Node.js 原生的 http 模組本身就可以建立 HTTP Server 和 API,只是很多事情需要自己處理,例如判斷 Method、判斷 URL、設定 Status Code 與 Response Header、使用 JSON.stringify() 轉換 JSON,以及透過 res.end() 送出並結束 Response。

Express 則建立在 Node.js 之上,把 Web 開發中經常出現的操作包裝成比較方便使用的 API。其中 app.get() 可以處理特定 Method 與 Route,res.status() 可以設定 Status Code,而 res.json() 則會處理 JSON 轉換、設定適合的 Content-Type、送出資料並結束這次 Response,因此不需要再自己使用 JSON.stringify()、設定 JSON 的 Content-Type 或另外呼叫 res.end()

到了這裡,我們已經正式建立了一個 Express Server。不過目前的 /todos 還只是固定回傳一段資料,如果真的要完成 Todo List,下一步就需要開始處理真正的資料,逐步建立 GET /todosPOST /todosPATCH /todos/:idDELETE /todos/:id,讓這支 Todo API 真的能夠新增、讀取、修改與刪除 Todo。


上一篇
Day 04|在學 Express 前,先搞懂 API 是什麼
下一篇
Day 06|CRUD 集合!完成第一組 RESTful API
系列文
出發吧!後端菜鳥:30 天的後端學習紀錄10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言