這一課完成後:你會在本機啟動第一個 Node.js HTTP server,建立 GET /api/hello route,回傳 JSON,並能說明 frontend、backend、request、response、status code 與 route 各自扮演什麼角色。

先把「Web App」拆成兩邊

到目前為止,你主要寫的是 frontend:HTML、CSS、JavaScript 都會被送到瀏覽器,最後在使用者的裝置上執行。

Backend 則是另一個正在執行的程式。它通常跑在 server 環境,負責接 request、做規則判斷、讀寫 database、處理 authentication,再把結果回給 client。

Browser / Frontend → HTTP Request → Backend
Backend → HTTP Response → Browser / Frontend

注意:backend 不代表一定在「很遠的雲端」。今天它就跑在你的電腦上,只是使用不同 process 和 port。

建立新的 Web App 練習專案

不要繼續把東西塞進 Web Basics 專案。建立:

web-app-playground/
└─ server.js

用 VS Code 打開整個資料夾,再開 Terminal。

先確認:

node --version

能看到版本號再繼續。如果找不到 Node.js,先回 Start Here 的環境設定。

第一個 server:先只回一段文字

在 server.js:

const { createServer } = require("node:http");

const port = 3000;

const server = createServer((request, response) => {
  response.writeHead(200, {
    "Content-Type": "text/plain; charset=utf-8"
  });

  response.end("Hello from backend.");
});

server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

執行:

node server.js

Terminal 不會立刻回到新的提示符號,因為這支程式不是「跑完就結束」;它會持續監聽 request。

node server.js → process 啟動 → listen 3000 → 等待 request

現在真的去 request 它

瀏覽器打開:

http://localhost:3000

你會看到:

Hello from backend.

這句文字不是瀏覽器自己知道的,也不是寫在 HTML 裡。瀏覽器送了一個 HTTP request,你的 Node.js process 收到後建立 response。

localhost 和 port 到底是什麼?

localhost 指向目前這台電腦。現在 client 和 server 剛好都在同一台機器上。

3000 是 port,可以先把它理解成這台電腦上某個網路服務正在監聽的入口。

localhost:3000 → 這台電腦上的 3000 port → 你的 Node server

之後正式部署時,網址可能變成真正的 domain,但 request / response 的基本模型不會因此改變。

request 和 response 不是抽象名詞

在 handler 裡:

createServer((request, response) => {
  // request:瀏覽器送進來的資訊
  // response:你準備回出去的內容
});

request 會包含 method、URL、headers 等資訊;response 則讓你設定 status、headers 與 body。

先加:

console.log(request.method, request.url);

重新整理瀏覽器,你的 server Terminal 會看到類似:

GET /
GET → HTTP method
/ → request path

Backend 不應該每個網址都回同一個東西

現在加入第一個真正的 route:

const { createServer } = require("node:http");

const port = 3000;

const server = createServer((request, response) => {
  if (request.method === "GET" && request.url === "/api/hello") {
    response.writeHead(200, {
      "Content-Type": "application/json; charset=utf-8"
    });

    response.end(JSON.stringify({
      message: "Hello from backend",
      source: "Norelyn Learn"
    }));
    return;
  }

  response.writeHead(404, {
    "Content-Type": "application/json; charset=utf-8"
  });

  response.end(JSON.stringify({
    error: "Not found"
  }));
});

server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

重新啟動 server 後,打開:

http://localhost:3000/api/hello

你應該會看到 JSON。

為什麼要重新啟動 server?

目前我們直接用 node server.js。你修改檔案並儲存後,已經在執行中的 Node process 不會自動重新讀完整程式。

先在 Terminal 按:

Ctrl + C

停止 process,再重新:

node server.js

之後可以再使用 watch mode 或開發工具自動重啟;現在先把 process 的概念弄清楚。

Route 是「哪一種 request 要交給哪段邏輯」

這次的 route 條件是:

GET /api/hello

意思不是「有一個叫 API 的資料夾」。它只是你定義的 HTTP 介面。

Method + Path → Route → Handler → Response

下一課會把 route 擴充成多個資源,並開始接 query、body 和不同 HTTP methods。

Status code 也是 response 的一部分

成功 route 回:

200

找不到 route 則回:

404

這些不是給人看的裝飾數字,而是 client 判斷 request 結果的重要訊號。

你在 Web Basics 已經寫過:

if (!response.ok) {
  throw new Error(...);
}

現在你站到 backend 這一側,開始負責決定什麼時候應該回成功、什麼時候應該回錯誤。

Content-Type 告訴 client:body 是什麼

回 JSON 時我們設定:

"Content-Type": "application/json; charset=utf-8"

然後:

JSON.stringify({ message: "Hello" })

因為 HTTP response body 最後要送的是位元組 / 文字內容,不是 JavaScript object 本身。

JavaScript object → JSON.stringify → response body → client → response.json()

這和上一條路徑學的 response.json() 剛好是一來一回。

用 curl 看 request / response,不一定每次都開瀏覽器

保持 server 正在執行,再開另一個 Terminal:

curl http://localhost:3000/api/hello

你應該會拿到 JSON。

如果要看 response headers,可以試:

curl -i http://localhost:3000/api/hello

這會讓 status line、headers 與 body 一起出現。之後測 API 時,瀏覽器不是唯一工具。

故意打一個不存在的 route

打開:

http://localhost:3000/api/does-not-exist

你應該拿到:

{"error":"Not found"}

而且 status 是 404。

這件事很重要:backend 不只負責「成功時吐資料」,還要定義失敗時的行為。

Frontend code 和 Backend code 最大差別之一:執行位置

script.js 送到 browser 後,使用者能下載、查看並執行它。

server.js 則在 server process 裡執行,不需要被直接送給使用者。

Frontend JS → Browser 執行
Backend JS → Server runtime 執行

這也是為什麼 database credentials、server-side secret 之後應該留在 backend environment,而不是寫進公開前端。

但不要把「backend」理解成自動安全。如果你自己把 secret 放進 API response、log、錯誤訊息或公開 repository,它照樣會洩漏。執行位置只是邊界的一部分。

你的 backend 現在其實還沒有 database

目前 server 每次 response 都是程式碼當場組出來的。

如果你把:

let count = 0;

放進 server 記憶體,它也只會活到 process 結束為止。重新啟動後資料就回到初始狀態。

這就是後面 database 要解決的問題:讓重要資料不要跟著 process 一起消失。

小挑戰:自己加一個 status endpoint

需求 1:新增 GET /api/status。

需求 2:回傳 JSON,不是純文字。

需求 3:至少包含 ok: true 和一個你自己命名的欄位。

需求 4:不存在的 route 仍然必須維持 404。

需求 5:用瀏覽器或 curl -i 確認 status code 與 Content-Type。

不要把兩個 route 寫成「不管 URL 是什麼都回 status」。你的 server 要真的根據 method + path 做不同決定。

最後收斂:你剛才建立的不是一個頁面

你建立的是一個持續執行、等待外部 request 的程式:

Node process
↓ listen port 3000
收到 HTTP request
↓ route 判斷
執行 backend logic
↓ HTTP response

Frontend 之後只是其中一種 client。手機 App、另一個 server、CLI,甚至測試工具都可以呼叫同一個 HTTP API。

下一課會讓 API 不只「回固定資料」,而是開始讀 request 裡的 path、query 和 body,處理真正的輸入。

完成條件:你能獨立啟動一個 Node.js HTTP server,建立至少兩個 GET routes,正確回傳 JSON 與 404,並能解釋 request、response、route、status code、frontend 與 backend 的關係。