Chapter 06

用最小範例看懂 Web server 到底在做什麼

真正的學習通常發生在「理論與範例接起來」的那一刻。 本章會用兩種常見語言的最小伺服器範例,讓你看到:程式如何接住 request、決定回應內容、設定 Content-Type,最後把結果交給瀏覽器;如果你原本習慣從 Apache 或 Nginx 的設定檔來理解網站,這一章會幫你把 Web server、Node.js Runtime 與 Framework 的位置重新排好。

先選你的閱讀路徑

純初學者

  • 先看 Python 最小伺服器。
  • 接著直接看 Express 範例。
  • 最後再回頭補原生 Node.js 的底層版本。

想看底層怎麼運作

  • 依序看 Python → 原生 Node.js → Express。
  • 比較 http.createServer()app.get() 的差異。
  • 再看三層拆解總結觀念。

熟悉 Apache / Nginx

  • 先看傳統觀念對照表。
  • 再看 Nginx / Apache 與 Express 的分工想像。
  • 最後回頭看原生 Node.js 與 Express 的差異。

如果你原本熟悉 Apache / Nginx,該怎麼重新理解?

你熟悉的傳統觀念 在現代 Web 架構中的對應 例子
VirtualHost / server block 站點入口與反向代理設定 server_namelistenlocation /api
CGI / FastCGI / PHP-FPM / uWSGI 讓應用程式跑起來的執行層(Runtime / Process) node server.jsuvicorn app:app
Rewrite / ProxyPass 把特定路徑轉送到應用服務 proxy_pass http://127.0.0.1:3000
應用程式框架 真正處理路由、驗證與商業邏輯的層 Express、NestJS、FastAPI、Django
對照記憶法: 如果你熟悉 Apache / Nginx + PHP-FPM,可以把 Node.js 想成「讓 JavaScript 應用能跑起來的執行環境」; 而 Express / NestJS 的角色,更接近 Laravel / Symfony 這類框架,負責組織路由、驗證與商業邏輯。

Web server 的基本職責

接收請求

在固定 IP / Port 上等待 request 進來。

判斷路徑

根據 URL path 判斷要回傳檔案、資料或錯誤訊息。

回傳正確內容

設定狀態碼與 Content-Type,讓瀏覽器知道如何處理。

MIME Types:瀏覽器如何知道你回了什麼?

內容 MIME Type 常見用途
HTML text/html 網頁結構
CSS text/css 樣式表
JavaScript text/javascript 前端腳本
JSON application/json API 回應資料
PNG image/png 圖片資源

Example 1:Python 最小 HTTP Server

用 Python 直接回傳 HTML

這個例子很適合教學,因為它夠短,但已經能看出 request 與 response 的結構。

from http.server import BaseHTTPRequestHandler, HTTPServer

class DemoHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.end_headers()
        self.wfile.write(b"<h1>Hello from Python</h1>")

server = HTTPServer(("127.0.0.1", 8000), DemoHandler)
print("Serving on http://127.0.0.1:8000")
server.serve_forever()
$ python app.py Serving on http://127.0.0.1:8000

Hello from Python

  • send_response(200) 設定狀態碼。
  • send_header() 告訴瀏覽器回傳的是 HTML。
  • wfile.write() 寫出真正的回應內容。

進階補充:原生 Node.js Runtime 建立最小伺服器

使用內建 http 模組

如果你想知道 Express 背後更底層的作法,再回來看這個版本。純初學者可以先看下一個 Express 範例,再回頭比較兩者差異。

const http = require("http");

const server = http.createServer((request, response) => {
  response.writeHead(200, {
    "Content-Type": "application/json"
  });

  response.end(JSON.stringify({
    message: "Hello from Node.js"
  }));
});

server.listen(3000, () => {
  console.log("Server on http://127.0.0.1:3000");
});
$ node server.js Server on http://127.0.0.1:3000
瀏覽器 / API 工具看到的回應
{
  "message": "Hello from Node.js"
}
  • 這裡不是回 HTML,而是回傳 JSON。
  • node server.js 的意思,是由 Node.js 這個 Runtime 去執行你的程式檔。
  • http.createServer() 代表你在 Runtime 裡建立了一個會處理 HTTP 的應用程式。
  • 只要 Content-Type 正確,瀏覽器或前端程式就知道怎麼處理。
一句話記住: Node.js 是讓 JavaScript 可以在伺服器端執行的 Runtime;Express、NestJS 等才是建立在 Node.js 之上的框架或框架式工具。

更詳細地說,Node.js 本身提供的是伺服器端 JavaScript 執行環境,以及檔案系統、網路、計時器、http 等 API。 當你執行 node server.js 時,是 Node.js 這個 Runtime 在執行你的應用程式; 如果你的程式裡再使用 Express 或 NestJS,才是把「如何定義路由、如何插入 middleware、如何切分 controller / service」這些工作交給框架來整理。

為什麼還要 Express?因為需求一多,手寫就會亂

情境 原生 Node.js Express
只有 1 條路由 還算容易,手寫 if (req.url === ...) 就能完成。 也很簡單,但結構會稍微多一點。
有 10 條以上路由 容易變成很長的條件判斷,維護困難。 可用 app.get()app.post() 與 router 拆分。
需要 middleware / 驗證 很多流程要自己手寫。 框架式工具通常已有成熟寫法與慣例。
教學重點: 原生 Node.js 很適合看懂底層,但真正寫多路徑 API 時,Express 這種框架式工具會讓程式更清楚,也更接近實務開發。

Example 2:先學會用 Express 宣告 API 路由

把路由宣告交給框架式工具

對多數初學者來說,這會比原生 Node.js 更像真正的應用開發,也更容易讀懂。

const express = require("express");

const app = express();

app.get("/api/status", (req, res) => {
  res.json({
    service: "ok",
    runtime: "Node.js",
    framework: "Express"
  });
});

app.listen(3000, () => {
  console.log("Express on http://127.0.0.1:3000");
});
$ node app.js Express on http://127.0.0.1:3000
GET /api/status
{
  "service": "ok",
  "runtime": "Node.js",
  "framework": "Express"
}
  • Node.js 仍然是底層的 Runtime,並沒有消失。
  • Express 幫你把 if (request.url === ...) 這種手動判斷包裝成更清楚的路由 API。
  • res.json()、middleware、router 等慣例,都是框架式工具帶來的便利。

Example 3:沒有框架時,要自己手動判斷路由

同一個 server,依路徑回不同內容

這一段是讓你回頭對照:如果不用框架,原來每一條路由都要自己手動判斷與回應。

if (request.url === "/") {
  response.end("<h1>Home Page</h1>");
} else if (request.url === "/about") {
  response.end("<h1>About Page</h1>");
} else {
  response.writeHead(404, { "Content-Type": "text/plain" });
  response.end("Not Found");
}
Request URL 回應結果
/ Home Page
/about About Page
/missing 404 Not Found
  • 「根據 URL 決定回應」就是最基本的 routing。
  • 框架把這件事包裝得更有結構,例如檔案式路由或裝飾器式路由。
  • 但本質上,它仍然是 request 進來後,選擇一個對應的處理方式。

從傳統 HTTP server 管理,到框架式服務的三層拆解

層次 代表工具 主要責任 對傳統管理者最容易混淆的點
入口層 Web server Nginx、Apache 接收連線、TLS、靜態檔、反向代理 它仍然存在,並沒有被 Node.js 取代。
執行層 Runtime Node.js、Python 讓應用程式碼能跑起來 它負責「執行程式」,不是幫你定義整個應用架構。
應用層 Framework Express、NestJS、FastAPI、Django 整理路由、驗證、middleware、模組與商業邏輯 這一層才是你從「手寫 server」進入「框架化開發」的地方。
給傳統 HTTP server 管理者的重點翻譯: 以前你可能習慣把很多事都看成 Apache / Nginx 的工作;現在要把「入口層」與「應用層」分開看。 Nginx / Apache 管入口與轉送,Node.js 負責跑程式,Express / NestJS 則負責把應用程式寫得有結構。

Summary:本章結論

  • Web server 的工作包含接收 request、判斷路徑、回傳狀態碼與正確的內容型別。
  • 純初學者可先用 Python → Express 建立整體印象,再回頭看原生 Node.js 的底層寫法。
  • Node.js 是 Runtime,不是 Framework;當你寫 http.createServer() 時,是你的應用程式利用 Runtime 建立了一個 server。
  • 對傳統 HTTP server 管理者來說,可以把 Nginx / Apache 視為入口層,把 Express / NestJS 視為應用層架構,中間由 Node.js 或 Python 這類 Runtime 承接執行。