📋 目錄
學會 React 之後,開始碰後端,卻發現一堆名詞完全陌生?Express、Koa、Hapi——這些框架到底在幫你做什麼?這篇文章從 Node.js 原生 HTTP 模組說起,讓你真正理解「框架」解決的問題,而不是只會跟著範例寫 code。
前言:為什麼要從原生開始?
多數前端工程師接觸後端,都是從 Express 開始的。跟著網路教學裝好框架、寫幾個路由,伺服器就跑起來了——但卻很難回答這個問題:「如果沒有 Express,這段程式碼會長什麼樣?」
這個問題重要,不是因為你要自己寫 HTTP 伺服器(你不会真的需要),而是因為:
- 除錯時離不開框架:錯誤訊息最終還是要回到原生 HTTP 的層次才能理解
- 理解框架的限制:知道邊界在哪裡,才知道什麼時候該繞路
- 快速掌握其他框架:Koa、Fastify、Hapi——這些框架解決的是同一組問題,只是做法不同
所以在學 Express 之前,先用 Node.js 原生 HTTP 模組寫一個最簡單的伺服器。
用原生 HTTP 模組寫一個伺服器
Node.js 有一個內建的 http 模組,不需要安裝任何套件就能用。
// server-native.mjs
import http from 'node:http';
// 建立 HTTP 伺服器
const server = http.createServer((req, res) => {
// req 是 incoming message,res 是 server response
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, 您好!');
});
// 監聽 3000 port
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
執行 node server-native.mjs,用瀏覽器打開 http://localhost:3000,就能看到「Hello, 您好!」。
但這個伺服器有幾個問題:
- 所有 request 都回傳同樣的內容——不管是
/、/about、還是/api/users,都回傳「Hello」 - 沒有路由概念——需要自己判斷
req.url是什麼 - 回傳的都是純文字——如果要回 JSON,需要自己序列化的邏輯
讓我們把路由處理加進去:
// server-with-router.mjs
import http from 'node:http';
const server = http.createServer((req, res) => {
const { method, url } = req;
// 簡單的路由判斷
if (url === '/api/users' && method === 'GET') {
const users = [
{ id: 1, name: '小明', role: 'admin' },
{ id: 2, name: '大華', role: 'user' },
];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(users));
} else if (url === '/api/users' && method === 'POST') {
// POST 處理需要讀取 request body,稍後再說
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Created!' }));
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
這樣就有了基本的路由——但你應該能感受到,光是「GET/POST 路由」這件事,就已經需要一堆 if/else 判斷了。如果再加上路徑參數(/api/users/:id)、查詢參數(?page=1)、錯誤處理、CORS 設定——這些基礎建設會佔掉你大部分的精力,而不是放在真正的業務邏輯上。
這就是 Express 存在的理由。
Express 登場:框架解決了什麼?
Express 的核心貢獻,是把 HTTP 處理的基礎建設抽象掉,讓你只專心在「收到什麼 request、回什麼 response」這件事上。
同樣的路由,用 Express 寫:
// server-express.mjs
import express from 'express';
const app = express();
// GET /api/users — 取得使用者列表
app.get('/api/users', (req, res) => {
const users = [
{ id: 1, name: '小明', role: 'admin' },
{ id: 2, name: '大華', role: 'user' },
];
res.json(users); // Express 自動幫你設定 Content-Type + JSON 序列化
});
// POST /api/users — 新增使用者
app.post('/api/users', (req, res) => {
const newUser = req.body; // Express 幫你解析 JSON body
// 實際應用會把 newUser 存進資料庫
res.status(201).json({ message: 'Created!', user: newUser });
});
// 404 處理
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
app.listen(3000, () => {
console.log('Express server on http://localhost:3000');
});
看出差別了嗎?用原生 HTTP,你需要自己判斷 method 和 url、自己設定 header、自己序列化 JSON。用 Express,這些全部幫你處理好,你只需要專心寫業務邏輯。
Express 實際幫你省掉的幾件事
| 處理的問題 | 原生 HTTP | Express |
|---|---|---|
| 路由 matching | 手動 if/else | app.get(), app.post() |
| 路徑參數 | 字串解析 | req.params.id |
| Query 參數 | URL parsing | req.query.page |
| Request body 解析 | stream 讀取 | express.json() middleware |
| JSON 回應 | 手動 JSON.stringify + header | res.json() |
| 404 處理 | 手動 else branch | app.use() 最後層 |
| CORS 設定 | 手動設定 header | cors middleware |
| 錯誤處理 | try/catch 到處寫 | app.use((err, req, res, next) => {}) |
Middleware 是 Express 最核心的概念。簡單來說,middleware 就是在請求和回應之間的「關卡」,每個關卡都可以對 request 或 response 做事情:
import express from 'express';
import cors from 'cors';
const app = express();
// Middleware 1:解析 JSON body
app.use(express.json());
// Middleware 2:處理 CORS
app.use(cors());
// Middleware 3:自己寫的 logging middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} — ${res.statusCode} (${duration}ms)`);
});
next(); // 必須 call next() 才會進入下一個 middleware
});
// 路由
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: '小明' }]);
});
app.listen(3000);
Middleware 的順序很重要。Express 會由上往下依序執行每個 middleware,所以 express.json() 必須在最前面——否則你的路由 handler 收到 req.body 時會是 undefined。
前端視角:思維的轉變
對前端工程師來說,最大的認知轉換不是語法,而是**「誰觸發什麼」**。
前端:事件驅動 UI
前端的世界是由事件驅動的——使用者點擊按鈕、填寫表單、滾動頁面,這些事件觸發了你的程式碼。
// 前端:等待事件
const handleSubmit = async (formData) => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(formData),
});
const result = await response.json();
setUser(result);
};
後端:等待請求
後端伺服器的角色是被動等待——不知道什麼時候會有請求,不知道是誰發的,不知道裡面裝什麼。你能做的,是把「收到什麼請求 → 回什麼回應」這件事定義清楚。
// 後端:定義「收到什麼請求時該怎麼處理」
app.post('/api/users', async (req, res) => {
// req.body 裡有前端送來的資料
const { name, email } = req.body;
// 這裡會是資料庫操作、驗證、商業邏輯
const newUser = await db.users.create({ name, email });
// 回應
res.status(201).json(newUser);
});
這個「等待 → 處理 → 回應」的迴圈,就是 HTTP 伺服器的本質。Express 把這個迴圈寫得更好寫、更好維護,但底層運作的还是同一套邏輯。
常見問題
Q:Express 跟 Fastify、Koa 比起來怎麼選?
A:如果是剛開始學後端,Express 仍然是最好的選擇——生態最大、資源最多、多數時候面試問的也是 Express。Fastify 在效能上更好,但社群資源較少。Koa 是 Express 原作者後來做的,概念更乾淨,但需要自己組合 middleware(沒有 express.json() 這種內建的)。建議先熟悉 Express,再去看其他框架感受差異。
Q:需要學 TypeScript 嗎?
A:需要。Express 本身是 JavaScript,但現代後端開發幾乎都是 TypeScript。用 TypeScript 寫 Express 不只是「加型別」,而是讓你對於 request/response 的結構有更清晰的認知,對除錯和重構都有幫助。
Q:Express 適合多大的專案?
A:Express 本身很輕量,適合中小型專案。但它不強迫你用特定的架構,所以大了之後容易變成「義大利麵」。如果專案規模變大,建議用 TypeScript + 一致的資料夾結構(controller/service/repository 分層),不要把所有邏輯都寫在路由 handler 裡。
總結:框架讓你站在巨人的肩膀上
這篇文章的核心概念只有一個:框架解決的是 HTTP 處理的基礎建設,讓你專心在業務邏輯。
從原生 HTTP 到 Express,你學到的不是「怎麼用 Express」,而是:
- HTTP 伺服器的本質是什麼
- 路由、Middleware、request/response 這些概念從哪裡來
- 框架幫你省掉了什麼、隱藏了什麼
理解這些之後,你對後端的恐懼感會大幅降低,無論未來要用 Express、Koa 還是 Fastify,都能快速上手。
下一步,可以試著把 Express 加上資料庫(SQLite 是最簡單的起點),做一個完整的 CRUD API出來——那才是真正「後端思維」開始形成的時刻。
想了解 Node.js 2026 年的版本選擇,可參考 《Node.js 22 vs 24》,裡面有 LTS 選擇和原生功能更新的完整整理。