Express、koa 实现原理以及对比:中间件、路由、上下文模型分别是什么?
面试速答(30 秒版 TL;DR)
- Express 和 Koa 都是 Node Web 框架,但设计哲学明显不同。
- Express 更像“基于中间件栈的实用框架”:核心是按顺序执行
req, res, next中间件和路由层。 - Koa 更像“更薄的一层 HTTP 抽象”:核心是
ctx上下文对象和基于 Promise 的洋葱模型。 - Express 早期设计更偏回调风格,中间件默认是“线性往后传”;Koa 从一开始就围绕
async/await设计,天然适合写前后置逻辑。 - 面试一句话:Express 解决的是“怎么快速组织路由和中间件”,Koa 更强调“怎么把请求处理抽象得更干净、更可组合”。
心智模型:两个框架都在做“把请求交给一串函数”
不管是 Express 还是 Koa,本质都在做同一类事:
- 收到 Node 原生
http.createServer的请求。 - 构造一套更好用的请求/响应抽象。
- 把请求交给一串中间件和路由处理。
- 最终把结果写回原生
res。
差异主要在两点:
- 中间件执行模型 不一样。
- 请求上下文抽象 不一样。
先看两者的总体结构
先记共同主链路:原生 http -> 框架接管 -> 中间件 / 路由 -> 业务处理 -> 写回响应。
这张图看起来很普通,但面试官真正想听的是下面这两点:
- Express 的“中间件链”更偏 一个一个往后传。
- Koa 的“中间件链”是 可进入、可返回的 Promise 洋葱模型。
一、Express 的核心实现原理
1. 基于原生 http 再封装一层
Express 最终还是跑在 Node 原生 HTTP 服务之上。
可以把它简化理解成:
const http = require('node:http')
function createApp() {
const stack = []
function app(req, res) {
let index = 0
function next(err) {
const layer = stack[index++]
if (!layer) return
layer(req, res, next)
}
next()
}
app.use = function (fn) {
stack.push(fn)
}
return app
}
const app = createApp()
http.createServer(app).listen(3000)
真实 Express 当然更复杂,里面还会有:
ApplicationRouterRouteLayer
但面试时不用把源码类名全背出来,知道“Express 把中间件和路由都抽象成 layer,再按顺序匹配执行”就够了。
2. 中间件模型是 req, res, next
app.use((req, res, next) => {
console.log('middleware')
next()
})
核心特征:
req、res直接暴露给你,偏底层、偏命令式。- 调用
next()表示继续往后走。 - 如果你不
next(),也不结束响应,请求就会挂住。
3. 路由本质也是特殊中间件
app.get('/users', (req, res) => {
res.json([{ id: 1 }])
})
面试口径:
- Express 会先判断路径、方法是否匹配。
- 匹配成功后执行对应的 route handler。
- 所以路由可以理解成“带匹配条件的中间件层”。
二、Koa 的核心实现原理
1. Koa 仍然基于原生 HTTP,但中间件设计完全不同
Koa 可以被简化理解成:
const http = require('node:http')
function createApp() {
const middlewares = []
const app = {
use(fn) {
middlewares.push(fn)
},
callback() {
const fn = compose(middlewares)
return function (req, res) {
const ctx = createContext(req, res)
fn(ctx).then(() => respond(ctx))
}
},
}
return app
}
http.createServer(app.callback()).listen(3000)
这里最关键的是两件事:
- 先把原生
req、res封装成ctx。 - 再把中间件数组通过
compose组合成 Promise 链。
2. 中间件模型是 ctx, next
app.use(async (ctx, next) => {
console.log('before')
await next()
console.log('after')
})
这意味着:
- 你不再主要直接操作
req、res,而是操作ctx。 await next()让你天然拥有前置和后置逻辑。- 错误处理、日志统计、事务包裹更自然。
3. ctx 是 Koa 很重要的抽象
Koa 会把:
req封装成更易用的requestres封装成更易用的response- 再挂到统一的
ctx
所以你常写的是:
ctx.status = 200
ctx.body = { ok: true }
而不是像 Express 那样频繁直接 res.status(...).json(...)。
三、Express 和 Koa 的中间件差异为什么这么大
Express:线性传递
Express 的常见理解方式是:
- 执行当前中间件
- 调
next() - 继续下一个
如果要做“后置逻辑”,写起来往往不如 Koa 自然,因为早期设计不是围绕 Promise 洋葱模型展开的。
Koa:洋葱模型
Koa 的中间件会形成:
before1 -> before2 -> before3 -> after3 -> after2 -> after1
所以统一日志、异常处理、资源释放会很顺手。
四、对比表:面试最常用的说法
| 维度 | Express | Koa |
|---|---|---|
| 中间件签名 | req, res, next | ctx, next |
| 执行模型 | 顺序向后传递 | Promise 洋葱模型 |
| 异步编程风格 | 历史上更偏回调,现代也支持 async | 从设计上就围绕 async/await |
| 请求上下文 | 直接操作 req、res | 通过 ctx 统一抽象 |
| 后置逻辑书写 | 可以做,但不如 Koa 自然 | 非常自然 |
| 框架风格 | 功能更全、更直接 | 更轻、更薄、更克制 |
| 生态 | 更老、更成熟、历史包袱也更多 | 更简洁,常配合社区中间件使用 |
五、什么时候选 Express,什么时候选 Koa
更适合 Express 的场景
- 需要成熟、现成、开箱即用的生态。
- 团队成员对 Express 更熟。
- 项目中已经大量使用 Express 中间件。
更适合 Koa 的场景
- 希望中间件模型更现代、更清晰。
- 需要大量统一前后置逻辑,比如日志、鉴权、异常、响应包装。
- 团队能接受“框架更薄,很多能力自己组装”。
面试别说成“谁全面碾压谁”,更稳的表述是:
- Express 偏工程实用和生态积累。
- Koa 偏抽象设计和中间件模型优雅。
典型题 & 标准答法
Q1:Express 的实现原理是什么?
- 本质是基于 Node 原生 HTTP 服务封装。
- 内部维护中间件和路由栈。
- 请求到来后按顺序匹配 layer,并把
req、res、next传给处理函数。
Q2:Koa 的实现原理是什么?
- 也是基于 Node 原生 HTTP。
- 先构造
ctx上下文。 - 再通过
compose把中间件组装成 Promise 洋葱链。 - 最终把
ctx.body等结果写回响应。
Q3:Express 和 Koa 最大差别是什么?
- Express 更偏线性中间件和直接操作
req/res。 - Koa 更偏
ctx抽象和洋葱模型。 - 从可组合性和前后置逻辑体验来看,Koa 更优雅。
Q4:为什么很多人说 Koa 更先进?
- 主要是说它的中间件模型和异步抽象更现代。
- 不是说 Express 不能用,而是 Koa 在设计上更克制、更统一。
易错点 / 坑
- 把 Express 说成“没有中间件,只有路由”,这是错的。
- 把 Koa 说成“只是把
req/res改名成ctx”,这太浅了。 - 忽略两者中间件执行模型的差异,只会罗列 API。
- 把“谁更好”讲成绝对结论,而不结合团队生态和场景。
- 以为 Koa 内置了很多功能,实际上它故意保持更轻量。
速记要点(可背诵)
- 两者都基于 Node 原生
http。 - Express 核心是 layer 栈 +
req, res, next。 - Koa 核心是
ctx+compose+ 洋葱模型。 - Express 更偏实用生态,Koa 更偏抽象优雅。
- 真正的面试分水岭在于:你能不能把“中间件模型差异”讲透。