Koa 2 → Koa 3 迁移指南
基于 Koa 3.1.2 源码分析,适用于生产项目迁移参考
版本概览
Koa 2 Koa 3
──────── ────────────────────────────────
Node 7.6+ Node 18+(LTS 强制要求)
CJS only CJS + ESM 双模式
generator generator 支持完全移除
redirect() ctx.back() 替代 redirect('back')
querystring URLSearchParams(标准 API)
无内置 ALS 内置 AsyncLocalStorage 支持
破坏性变更(Breaking Changes)
1. Node.js 最低版本:>= 18
| Koa 2 | Koa 3 | |
|---|---|---|
| 最低版本 | Node.js >= 7.6.0 | Node.js >= 18.0.0 |
| 原因 | 仅需 async/await | 使用 Fetch API、Blob、ReadableStream 等原生标准 |
# 迁移前检查
node --version # 必须 >= 18.0.0
2. Generator 中间件彻底移除
Koa 1.x 风格的 generator 中间件在 Koa 2 中已废弃(需 koa-convert),Koa 3 直接删除,不再报错也不再兼容。
// ❌ Koa 1/2(generator 风格)—— Koa 3 中完全失效
app.use(function *(next) {
yield next;
});
// ✅ Koa 3 唯一支持的写法
app.use(async (ctx, next) => {
await next();
});
迁移动作:全局搜索 function *,全部改为 async/await,移除 koa-convert 依赖。
3. ctx.redirect('back') 移除 → 改用 ctx.back()
Koa 3 移除了 redirect('back') 的魔法字符串,新增更安全的 ctx.back() 方法(内置同源校验,防止开放重定向攻击)。
// ❌ Koa 2
ctx.redirect('back');
// ✅ Koa 3 —— 必须提供 fallback 路径
ctx.back('/');
ctx.back('/dashboard');
ctx.back() 的安全机制:
- 有
Referer头 → 检查是否同源,同源才重定向 - 无
Referer或跨域 → 重定向到alt参数(默认/)
4. ctx.origin 语义变更
// 请求头:Host: example.com, Origin: https://app.example.com
// Koa 2:返回当前服务器的 protocol + host
ctx.origin // → 'https://example.com'
// Koa 3:返回请求中的 Origin 头(无 Origin 头时返回 null)
ctx.origin // → 'https://app.example.com'
影响场景:CORS 检查、日志记录中用到 ctx.origin 的逻辑需要重新评估。
5. ENOENT 错误不再自动处理为 404
// Koa 2:fs 抛出 ENOENT 错误会被自动转为 404 响应
// Koa 3:ENOENT 会穿透到全局错误处理,需要手动判断
// ✅ Koa 3 迁移写法(在 error-handler 中)
if (err.code === 'ENOENT') {
ctx.status = 404;
ctx.body = { message: 'Not Found' };
return;
}
6. ctx.body = null 行为变更
// Koa 2
ctx.body = null; // → 204 No Content,无 Content-Length
// Koa 3
ctx.body = null; // → 设置 Content-Length: 0,状态码取决于上下文
// 若 Content-Type 是 application/json → body 写为字符串 "null"
7. QueryString 解析模块替换
底层从 Node.js 废弃的 querystring 模块切换到标准 URLSearchParams,解析行为有细微差异:
// 差异示例
// 输入:?a=1&a=2
// Koa 2(querystring)
ctx.query // → { a: ['1', '2'] }
// Koa 3(URLSearchParams)
ctx.query // → { a: '2' } ← 重复键只保留最后一个
影响场景:使用同名多值查询参数(如 ?ids=1&ids=2)的接口需要改用 ctx.request.querystring 手动解析。
新增特性
1. 内置 AsyncLocalStorage 支持
Koa 3 原生集成 AsyncLocalStorage,无需第三方库即可在任意位置访问当前请求上下文。
import Koa from 'koa';
// 启用内置 ALS
const app = new Koa({ asyncLocalStorage: true });
// 在任意模块(service、logger 等)获取当前请求 ctx
const ctx = app.currentContext;
本项目已在 src/core/context-storage.ts 中手动实现了同等功能,Koa 3 内置方案可作为替代。
2. 现代 Web 标准 Body 类型
ctx.body 现在支持 Fetch API 原生类型,无需手动转换:
// ✅ Koa 3 新增支持
// Blob
ctx.body = new Blob(['hello'], { type: 'text/plain' });
// ReadableStream(流式响应)
ctx.body = new ReadableStream({
start(controller) {
controller.enqueue('chunk1');
controller.close();
}
});
// Fetch API Response(直接代理上游响应)
const upstream = await fetch('https://api.example.com/data');
ctx.body = upstream; // 直接转发,含状态码和 headers
实际价值:做 BFF(Backend for Frontend)或 API 网关时,可以直接 ctx.body = await fetch(...) 转发上游响应,极大简化代码。
3. ESM 原生支持
// koa/package.json exports 字段
{
"exports": {
".": {
"require": "./lib/application.js", // CJS
"import": "./dist/koa.mjs" // ESM
}
}
}
// ✅ 直接使用 ESM import(本项目已采用此方式)
import Koa from 'koa';
4. HTTP/2 改进
// Koa 3 自动处理 HTTP/2 的 :authority 伪头
// request.js 内部逻辑:
if (req.httpVersionMajor >= 2) {
host = this.get(':authority'); // HTTP/2
} else {
host = this.get('Host'); // HTTP/1.1
}
// response.js:HTTP/2 不发送 statusMessage(协议不支持)
if (req.httpVersionMajor < 2) {
res.statusMessage = statuses.message[code];
}
TypeScript 类型变更
ctx.body 类型从 any 改为 unknown
// Koa 2 @types/koa
body: any;
// Koa 3 @types/koa
body: unknown;
这意味着直接读取 ctx.body 的代码需要类型断言或类型守卫:
// ❌ Koa 3 中会报类型错误
const id = ctx.body.id;
// ✅ 使用泛型 ParameterizedContext 约束响应类型
import type { ParameterizedContext } from 'koa';
type MyCtx = ParameterizedContext<
DefaultState,
DefaultContext,
{ id: number; name: string } // ResponseBodyT
>;
迁移检查清单
环境准备
- Node.js 升级到 >= 18.0.0
- 更新
package.json中engines字段 - 更新
koa到^3.0.0 - 更新
@types/koa到^3.0.0 - 移除
koa-convert(不再需要)
代码改造
- 搜索
function *,全部改为async/await - 搜索
redirect('back'),改为ctx.back('/fallback') - 检查
ctx.origin的使用,确认语义是否变化 - 检查 ENOENT 错误处理,在 error-handler 中补充 404 逻辑
- 检查同名多值查询参数(
?a=1&a=2)的解析逻辑 - 检查
ctx.body = null的处理逻辑 - 修复
ctx.body类型从any改为unknown带来的 TS 错误
依赖检查
- 检查所有 Koa 中间件是否支持 Koa 3(重点检查
koa-router→@koa/router) - 检查是否有中间件内部使用了
koa-convert
中间件生态兼容性
| 中间件 | Koa 3 状态 | 说明 |
|---|---|---|
@koa/router | ✅ 兼容 | 官方维护,推荐替代 koa-router |
koa-bodyparser | ✅ 兼容 | 继续可用 |
koa-static | ⚠️ 注意 | 需手动处理 ENOENT → 404 |
koa-cors / @koa/cors | ✅ 兼容 | - |
koa-router(非官方) | ⚠️ 停止维护 | 迁移到 @koa/router |
koa-convert | ❌ 移除 | Koa 3 不再需要 |
koa-compose | ✅ 兼容 | Koa 3 内部依赖 |
变更速查表
| 特性 | Koa 2 | Koa 3 | 操作 |
|---|---|---|---|
| Node.js 版本 | >= 7.6 | >= 18 | 升级环境 |
| Generator 中间件 | 废弃警告 | 完全移除 | 改写代码 |
redirect('back') | 支持 | 移除 | → ctx.back() |
ctx.origin | 服务器 origin | Request Origin 头 | 检查逻辑 |
| ENOENT → 404 | 自动 | 手动 | 补充处理 |
ctx.body = null | 204 | 取决于 Content-Type | 检查逻辑 |
| QueryString 重复键 | 数组 | 最后一个值 | 检查接口 |
ctx.body 类型 | any | unknown | 修复 TS |
| Blob/ReadableStream | 不支持 | 原生支持 | 按需使用 |
| AsyncLocalStorage | 需第三方 | 内置 | 按需迁移 |
| ESM | 有限 | 原生 | 按需使用 |
| HTTP/2 | 基础 | 增强 | 按需使用 |