Koa 與 Express 的核心差異

前言:
最近在公司被分派一個 Koa backend 的功能開發任務,雖然我平時多數時間主要負責前端相關的工作,不過因為學生時期有稍微接觸過一點 Express,它與 Koa 都是 Node.js 知名 web 框架,所以閱讀程式碼時並不會太陌生。這兩者雖然有不少相似之處,但實際使用後發現,它們在設計理念和使用體驗上有很大的不同。
簡介
Express 是目前 Node.js 最知名且下載量最多的 web 框架。誕生於 2010 年,它是當時第一批專為 Node.js 設計的 Web 框架之一。因為簡單易學、功能強大,再加上豐富的社群資源,Express 很快成為開發者的首選工具,也推動了 Node.js 生態系統的成長,甚至啟發了後來許多框架的設計。
到了 2013 年,Express 的開發團隊推出了一個新框架:Koa。Koa 主打簡潔與輕量,移除了內建中介軟體,讓開發者可以自行選擇需要的模組來打造應用。Koa 的一大特色是原生支援 async/await,解決了 Express 在同步與異步操作上的一些限制。相較之下,Express 的中介軟體基於 callback 設計,內部的 next() 函數是同步執行,無法像 Koa 那樣等待下層中介軟體完成後再繼續執行。這點我們稍後會在後續的章節透過範例來詳細說明。
這篇文章整理了我的學習心得,希望能帶大家深入了解 Express 和 Koa 的核心差異。如果你和我一樣在查資料時曾經有「哦,原來是這樣!」的驚喜,期待這篇文章能再次帶給你那樣的體驗。
框架的內建功能
Express:開箱即用
如果你使用過 Express,那你一定知道它最大的優點就是「方便」。Express 是一個接近於完整的框架,它內建了不少功能,讓開發者可以快速啟動專案,少去找第三方套件的麻煩。Express 還附帶了許多便捷方法來處理各種需求,例如:
express.Router():提供靈活的路由管理。express.static():方便地設定靜態文件服務。express.set():用於應用設定。express.json()和express.urlencoded():內建的 body parser,讓你能輕鬆解析 JSON 和 URL 編碼的請求體。
簡單看個範例:
const express = require('express');
const app = express();
app.use(express.json()); // 內建 body parser
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
app.listen(3000, () => console.log('Express server running on http://localhost:3000'));
這段程式碼展示了 Express 如何快速設定一個簡單的 HTTP 伺服器。只需要幾行程式碼,不需要下載額外的第三方套件,就能處理基本的路由和請求解析。
Koa:極簡核心
反觀 Koa,它的設計理念是「保持核心極簡」。換句話說,Koa 的核心不包含任何預設的中介層,連基本的路由和 body parser 都需要你自己安裝。這樣的設計讓開發者能夠有極高的自由度,按照自己的需求去組裝應用程式。
Koa 特別適合那些不依賴大量路徑管理的 HTTP 服務,比如 Webhook 或聊天機器人,因為它不會預設任何結構,開發者可以靈活選擇所需的中介軟體來實現功能。
舉個簡單的例子來看看如何在 Koa 中新增路由和 body parser:
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
app.use(bodyParser()); // 需要手動引入 body parser
router.get('/', (ctx) => {
ctx.body = 'Hello from Koa with Router and Body Parser!';
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => console.log('Koa server running on http://localhost:3000'));
可以看到,使用 Koa 時需要我們手動安裝 @koa/router 和 koa-bodyparser 等套件,來補足 Express 內建的功能。這樣的設計讓 Koa 的核心保持簡潔,並給予開發者極大的掌控權。當然,自由也意味著在搭建應用時需要更多的決策和設置,這對於習慣快速啟動專案的人來說,可能需要適應一下。
Request/Response 處理方式
Express:使用 req 和 res 物件
在 Express 中,處理請求和回應的核心是 req(Request)和 res(Response)物件。這兩個物件由 Express 根據 Node.js 原生的 http.IncomingMessage 和 http.ServerResponse 包裝而來,並添加了更多方便開發的屬性與方法。
req 提供了各種屬性和方法,用於存取請求相關的數據,例如:
req.body:存取 POST 請求的資料。req.params:存取路由中的參數。req.query:存取 URL 中的查詢參數。
res 物件則用來處理回應,包括:
res.send():發送回應。res.json():發送 JSON 格式的回應。res.status():設置 HTTP 狀態碼。
以下是一個簡單的 Express 範例,展示如何處理請求和回應:
const express = require('express');
const app = express();
app.use(express.json()); // 解析 JSON 請求體
app.get('/greet/:name', (req, res) => {
const name = req.params.name;
res.status(200).json({ message: `Hello, ${name}!` });
});
app.listen(3000, () => console.log('Express server is running on http://localhost:3000'));
Koa:使用 ctx 上下文物件
Koa 的 Request/Response 處理則是基於 ctx(Context) 物件。ctx 封裝了 request 和 response,並將它們整合到一個乾淨的 API 中,讓開發者更方便地操作。
每個請求都會生成一個獨立的 ctx,開發者可以直接在這個物件上存取或修改請求與回應。Koa 將 Node.js 原生的 req 和 res 包裝成 ctx.request 和 ctx.response,並提供了額外功能。
在 Koa 中,常見的請求操作包括:
ctx.request.body:存取 POST 請求的數據(需搭配 koa-bodyparser)。 ctx.params:存取路由參數(需搭配路由模組,如 @koa/router)。 ctx.query:存取 URL 查詢參數。 回應操作則包括:
ctx.body:設置回應內容。 ctx.status:設置 HTTP 狀態碼。
以下是一個簡單的 Koa 範例:
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
app.use(bodyParser()); // 解析 JSON 請求體
router.get('/greet/:name', (ctx) => {
const name = ctx.params.name;
ctx.status = 200;
ctx.body = { message: `Hello, ${name}!` };
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => console.log('Koa server is running on http://localhost:3000'));
除了 ctx.request 和 ctx.response,ctx 還包括許多方便的 helper methods 與屬性,讓開發者更輕鬆處理常見任務。以下是一些實用的例子:
ctx.querystring:返回原始的查詢字符串。例如,對於 URLhttp://localhost:3000/greet?name=John&age=30,ctx.querystring會返回"name=John&age=30"。ctx.throw(status, message):主動拋出錯誤,簡化錯誤處理。例如,ctx.throw(400, 'Bad Request')會在錯誤發生處立即中斷執行後續的中介軟體,並返回 400 狀態碼和錯誤訊息。ctx.state:一個共享物件,用於在中介軟體之間傳遞數據。例如,可以在認證中介軟體中設定用戶資訊,供後續的業務邏輯使用。ctx.is(types):檢查請求的 Content-Type 是否匹配指定類型。例如,ctx.is('json', 'text')可判斷請求是否為 JSON 或純文本。