本文将带你从零开始,系统讲解 Koa 的核心概念与常用功能,并结合多个实战示例,帮助你在真实项目中快速应用和扩展。
1. 简介与安装
Koa 由 Express 原班人马打造,目标是打造更简洁、可组合、基于现代 JS 特性的 Web 框架。它舍弃了内置中间件,鼓励根据需求灵活引入第三方中间件,因此更轻量。使用 async/await 天然支持异步,代码更直观。
安装命令:
npm init -y
npm install koa koa-router koa-bodyparser koa-static koa-logger koa-cors jsonwebtoken koa-jwt @koa/schema koa-validate @koa/multer multer
核心依赖:
koa:框架核心koa-router:路由管理koa-bodyparser:解析 JSON、表单数据koa-static:静态文件服务koa-logger:请求日志koa-cors:跨域支持jsonwebtoken、koa-jwt:JWT 鉴权@koa/schema/koa-validate:参数校验@koa/router路由管理,用上面人选其一都行,因为koa-router很久没更新了。@koa/multer用来处理表单中的 文件上传(multipart/form-data) 类型请求
2. 快速入门:Hello Koa
入门使用
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = '👋 Hello, Koa!';
});
app.listen(3000, () => console.log('Server: http://localhost:3000'));
搭配router使用
// app.js
const Koa = require('koa');
const Router = require('koa-router');
// const Router = require('@koa/router');//根据你的选择进行引入使用
const app = new Koa();
const router = new Router();
router.get('/', async ctx => {
ctx.body = '👋 Hello, Koa!';
});
app
.use(router.routes())
.use(router.allowedMethods());
app.listen(3000, () => console.log('Server: http://localhost:3000'));
启动后访问根路径,输出 "👋 Hello, Koa!"。
3. 中间件机制与洋葱模型
Koa并没有提供methods的方式来注册中间件也没有提供path中间件来匹配路径
Koa 的核心在于洋葱模型(Onion Model):每个中间件可在执行前后控制流程。中间件通过 await next() 将执行权交给下一个,之后继续执行自身后续代码。
简单来说就是:
- 中间件是 一层一层嵌套调用的。
- 请求时是从外到里一层层进入。
- 响应时是从里到外一层层返回。
请求 --> [ 中间件1 --> 中间件2 --> 中间件3 --> 处理核心 ]
<-- <-- <--
响应 响应 响应
每个中间件一般长这样:
async function(ctx, next) {
// 进入这一层
console.log('进入中间件1');
await next(); // 调用下一层
// 等下一层执行完再回到这里
console.log('返回中间件1');
}
这样就像剥洋葱一样,一层层进去,再一层层出来。
✅ 举个例子:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('中间件1:进入');
await next();
console.log('中间件1:返回');
});
app.use(async (ctx, next) => {
console.log('中间件2:进入');
await next();
console.log('中间件2:返回');
});
app.use(async (ctx) => {
console.log('中间件3(核心处理):处理请求');
ctx.body = 'Hello Koa';
});
app.listen(3000);
运行后访问浏览器,你会看到控制台输出:
中间件1:进入
中间件2:进入
中间件3(核心处理):处理请求
中间件2:返回
中间件1:返回
是不是很像洋葱的结构?🌰
这种模式有利于切面编程:统一日志、鉴权、异常捕获等。
4. 参数解析
ctx 上下文
console.log(ctx.request);//koa 的 Request 对象.
console.log(ctx.req);//Node 的 request 对象.
查询参数(Query)
router.get('/search', async ctx => {
const { keyword = '', page = '1' } = ctx.query;
ctx.body = { keyword, page: Number(page) };
});
路径参数(Params)
router.get('/user/:id', async ctx => {
const { id } = ctx.params;
ctx.body = { userId: id };
});
请求体(Body)与文件上传
//使用第三方中间件解析参数
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
router.post('/login', async ctx => {
//注意事项:不能从ctx.body中获取数据
const { username, password } = ctx.request.body;
//这里使用的解析可以拿到ctx.request.body。但是ctx.req.body拿不到,有一些库可以拿到这些。
//console.log(ctx.request.body, ctx.req.body)
// 业务处理...
ctx.body = { username };
});
上文请求不能解析form-data数据
如需文件上传,可使用 koa-multer:
const multer = require('@koa/multer');
const upload = multer({ dest: 'uploads/' });
// upload.single('file')替换为upload.any()即可解析form-data数据参数
router.post('/upload', upload.single('file'), async ctx => {
ctx.body = { filename: ctx.file.filename };
});
//多文件上传 文件名字需自定义可以参考
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null,'uploads'); // 保存路径
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + file.originalname);
}
});
const upload = multer({ storage: storage });
router.post('/upload', upload.array('file', 3), async ctx => {
const files = ctx.files; // 是一个数组
ctx.body = {
files: files.map(file => ({
originalname: file.originalname,
filename: file.filename,
path: file.path,
size: file.size
}))
};
});
多文件上传参考上一篇文章节点上传文件实例
参数校验(Validation)
使用 @koa/schema:
const { Joi, validate } = require('@koa/schema');
const loginSchema = Joi.object({
username: Joi.string().min(3).required(),
password: Joi.string().min(6).required()
});
router.post('/login', validate({ body: loginSchema }), async ctx => {
// ctx.request.validatedBody 已通过校验
ctx.body = { ok: true };
});
5. 响应与错误处理
ctx 上下文
console.log(ctx.res);//Node 的 response 对象.
console.log(ctx.response);//koa 的 Response 对象.
基本响应
ctx.body:设置响应体(string、Buffer、object)ctx.status:设置 HTTP 状态码ctx.set(name, value):设置头部
router.get('/info', async ctx => {
ctx.status = 200;
ctx.set('X-Server', 'Koa');
ctx.body = { uptime: process.uptime() };
});
全局错误处理
// 最先注册
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
ctx.app.emit('error', err, ctx);
}
});
// 监听错误
app.on('error', (err, ctx) => {
console.error('❌ Server Error', err);
});
自定义 Error 类
class HttpError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
router.get('/secret', async ctx => {
throw new HttpError(401, 'Unauthorized');
});
6. 常见中间件实践
- koa-logger:自动输出请求与响应时间
const logger = require('koa-logger');
app.use(logger());
- koa-cors:跨域支持
const cors = require('@koa/cors');
app.use(cors({ origin: '*' }));
- koa-ratelimit:限流
const RateLimit = require('koa-ratelimit');
app.use(RateLimit({
driver: 'memory',
db: new Map(),
duration: 60000,
errorMessage: 'Too many requests',
max: 100
}));
7. 静态文件服务与高级优化
静态服务器
安装所需依赖:
npm install koa-static
创建一个名为 public 的目录,并添加一些示例文件:
koa-static-server/
├── public/
│ ├── index.html
│ ├── style.css
│ └── logo.png
├── app.js
└── package.json
示例index.html 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Koa 静态服务器</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello Koa!</h1>
<img src="logo.png" width="100" />
</body>
</html>
基本使用
const Koa = require('koa');
const static = require('koa-static');
const path = require('path');
const app = new Koa();
// 指定静态资源目录
const staticPath = path.join(__dirname, 'public');
app.use(static(staticPath));
// 启动服务
const port = 3000;
app.listen(port, () => {
console.log(`静态服务器已启动:http://localhost:${port}`);
});
将 public/ 下资源映射到根路径。
可以传入配置项来设置缓存、默认首页等:
app.use(static(staticPath, {
maxage: 1000 * 60 * 60 * 24, // 缓存一天
index: 'index.html',
gzip: true
}));
缓存与压缩
const compress = require('koa-compress');
app.use(compress());
// 配合缓存头
app.use(ctx => ctx.set('Cache-Control', 'public, max-age=31536000'));
SPA 前端路由支持
app.use(async (ctx, next) => {
await next();
if (ctx.status === 404 && !ctx.path.startsWith('/api')) {
ctx.type = 'html';
ctx.body = fs.createReadStream('./public/index.html');
}
});
8. Koa 源码核心剖析
-
Application 初始化:在
lib/application.js中,构建middleware数组与context原型。 -
compose:使用如下伪码串联中间件:
function compose(middleware) { return function(ctx) { return dispatch(0); function dispatch(i) { const fn = middleware[i]; if (!fn) return Promise.resolve(); return Promise.resolve(fn(ctx, () => dispatch(i+1))); } }; } -
Context 对象:
createContext(req, res)将原生req,res包装,注入ctx.request、ctx.response,并将两者代理到ctx。 -
Request/Response:分别封装 Node 原生接口,提供更友好的属性与方法,如
ctx.request.query、ctx.response.type。
9. Koa 与 Express 深度对比
| 特性 | Express | Koa |
|---|---|---|
| 核心对象 | req、res | ctx(统一请求与响应) |
| 中间件机制 | 回调 + next(err) | Promise + async/await 洋葱模型 |
| 内置功能 | 路由、静态、模板、错误处理 | 仅提供最小核心,需按需组合中间件 |
| 性能 | 稍逊 | 更轻量,性能略优 |
| 错误处理 | 单次回调链,需手动捕获 | 全局集中捕获,抛出异常即可 |
| 学习曲线 | 统一,生态成熟 | 灵活,需了解中间件组合 |
10. 实战案例:RESTful API + JWT 鉴权
-
目录结构
jwt-app/ ├─ app.js ├─ routes/ │ ├─ auth.js │ └─ users.js ├─ controllers/ │ ├─ auth.js │ └─ users.js └─ middleware/ └─ auth.js -
生成与校验 Token
// controllers/auth.js
const jwt = require('jsonwebtoken');
const secret = 'YOUR_SECRET';
exports.login = async ctx => {
const { username, password } = ctx.request.body;
if (username === 'admin' && password === '123') {
const token = jwt.sign({ user: username }, secret, { expiresIn: '1h' });
ctx.body = { token };
} else ctx.throw(401);
};
- 中间件保护
// middleware/auth.js
const jwt = require('koa-jwt');
const secret = 'YOUR_SECRET';
module.exports = jwt({ secret });
// routes/users.js
const router = require('koa-router')();
const guard = require('../middleware/auth');
router.get('/profile', guard, async ctx => {
ctx.body = { user: ctx.state.user };
});
- 完整注册
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const app = new Koa();
app.use(bodyParser());
app.use(authRoutes.routes());
app.use(userRoutes.routes());
app.listen(4000, () => console.log('JWT API @4000'));
11. 总结与延伸阅读
- Koa 基于 async/await、洋葱模型,核心极简,可按需组合中间件
- 推荐中间件:路由、解析、日志、鉴权、校验、限流、静态、压缩
- 社区资源:
- 官方文档:koajs.com