Koa2 是由 Express 原班人马打造的新一代 Node.js Web 框架,它基于 ES6+ 的 async/await 语法,摒弃了回调地狱,中间件采用经典的洋葱模型,核心代码极其轻量(仅约 1500 行),所有功能都通过中间件扩展,灵活且强大。
本文将从环境准备、快速上手、核心概念,到路由、模板引擎、数据库、错误处理、安全、部署,全面讲解 Koa2 的用法,附完整可运行代码,帮你快速掌握 Koa2。
一、前置准备:环境要求
Koa2 依赖 async/await,因此对 Node.js 版本有要求:
- 推荐:Node.js v7.6.0+(原生支持 async/await)
- 旧版本兼容:Node.js v7.6.0 以下需配合 Babel 转译(不推荐,建议升级 Node.js)
检查 Node.js 版本:
二、快速上手:5分钟写出第一个 Koa2 应用
1. 初始化项目
1 2
| mkdir koa2-demo && cd koa2-demo npm init -y
|
2. 安装 Koa
1
| npm install koa@2 --save
|
3. 编写 Hello World 应用
创建 app.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => { ctx.body = 'Hello, Koa2!'; });
app.listen(3000, () => { console.log('Server is running at http://localhost:3000'); });
|
4. 启动应用
打开浏览器访问 http://localhost:3000,就能看到 Hello, Koa2! 了。
三、核心概念:理解 Koa2 的灵魂
Koa2 的核心只有两个:中间件(Middleware) 和 上下文(Context),其中中间件的洋葱模型是 Koa2 最经典的设计。
1. 中间件与洋葱模型
Koa2 的中间件是一个函数,通过 app.use() 注册,多个中间件会形成一个洋葱结构,执行顺序是:从外向内进入,再从内向外返回。
洋葱模型示例
创建 middleware-demo.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const Koa = require('koa'); const app = new Koa();
app.use(async (ctx, next) => { console.log('1. 进入中间件1'); await next(); console.log('5. 返回中间件1'); });
app.use(async (ctx, next) => { console.log('2. 进入中间件2'); await next(); console.log('4. 返回中间件2'); });
app.use(async (ctx) => { console.log('3. 进入中间件3(最内层)'); ctx.body = '洋葱模型演示'; });
app.listen(3000);
|
执行后访问页面,控制台输出顺序是:
1 2 3 4 5
| 1. 进入中间件1 2. 进入中间件2 3. 进入中间件3(最内层) 4. 返回中间件2 5. 返回中间件1
|
这就是经典的洋葱模型,next() 之前的代码在进入时执行,next() 之后的代码在返回时执行,非常适合做日志、响应时间统计、错误处理等。
2. 上下文对象 Context(ctx)
ctx 是 Koa2 的核心对象,它封装了 Node.js 的 request 和 response,提供了更简洁的 API。
常用属性与方法
| 属性/方法 |
说明 |
示例 |
ctx.request |
Koa 的 Request 对象 |
ctx.request.method、ctx.request.url |
ctx.response |
Koa 的 Response 对象 |
ctx.response.status |
ctx.req |
Node.js 原生 Request 对象 |
- |
ctx.res |
Node.js 原生 Response 对象 |
- |
ctx.body |
响应体(最常用) |
ctx.body = { data: 'ok' } |
ctx.status |
响应状态码 |
ctx.status = 404 |
ctx.query |
GET 查询参数(对象) |
ctx.query.name |
ctx.params |
路由动态参数(需配合路由中间件) |
ctx.params.id |
ctx.request.body |
POST 请求体(需配合 bodyParser 中间件) |
- |
ctx.cookies |
Cookie 操作 |
ctx.cookies.set('name', 'koa') |
ctx.throw() |
抛出错误 |
ctx.throw(400, '参数错误') |
四、路由:处理不同 URL 请求
Koa2 本身没有内置路由,需要使用第三方中间件,最常用的是 koa-router。
1. 安装 koa-router
1
| npm install koa-router --save
|
2. 基本路由用法
创建 router-demo.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const Koa = require('koa'); const Router = require('koa-router');
const app = new Koa(); const router = new Router();
router.get('/', async (ctx) => { ctx.body = '首页'; });
router.get('/user', async (ctx) => { const name = ctx.query.name || '访客'; ctx.body = `用户:${name}`; });
router.get('/user/:id', async (ctx) => { const id = ctx.params.id; ctx.body = `用户ID:${id}`; });
router.post('/user', async (ctx) => { const body = ctx.request.body; ctx.body = { message: '创建用户成功', data: body }; });
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);
|
3. 解析 POST 请求体:koa-bodyparser
上面的 POST 请求需要解析请求体,安装 koa-bodyparser:
1
| npm install koa-bodyparser --save
|
在路由之前注册:
1 2
| const bodyParser = require('koa-bodyparser'); app.use(bodyParser());
|
4. 路由前缀
给路由添加统一前缀,比如 /api:
1 2
| const router = new Router({ prefix: '/api' });
|
五、模板引擎:渲染动态 HTML
Koa2 可以配合多种模板引擎(如 EJS、Pug、Nunjucks),使用 koa-views 中间件。
1. 安装依赖
以 EJS 为例:
1
| npm install koa-views ejs --save
|
2. 配置模板引擎
创建 views-demo.js,并在项目根目录创建 views 文件夹,存放模板文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const Koa = require('koa'); const views = require('koa-views'); const path = require('path');
const app = new Koa();
app.use(views(path.join(__dirname, 'views'), { extension: 'ejs' }));
app.use(async (ctx) => { await ctx.render('index', { title: 'Koa2 模板引擎', message: 'Hello, EJS!', list: ['苹果', '香蕉', '橙子'] }); });
app.listen(3000);
|
3. 编写 EJS 模板
创建 views/index.ejs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title><%= title %></title> </head> <body> <h1><%= message %></h1> <ul> <% list.forEach(item => { %> <li><%= item %></li> <% }) %> </ul> </body> </html>
|
六、静态资源服务:托管 CSS、JS、图片
使用 koa-static 中间件托管静态资源。
1. 安装 koa-static
1
| npm install koa-static --save
|
2. 配置静态资源
创建 public 文件夹,存放静态文件(如 public/css/style.css):
1 2 3 4 5 6 7 8 9 10
| const Koa = require('koa'); const static = require('koa-static'); const path = require('path');
const app = new Koa();
app.use(static(path.join(__dirname, 'public')));
app.listen(3000);
|
现在访问 http://localhost:3000/css/style.css 就能看到静态文件了。
七、错误处理:统一处理异常
Koa2 的错误处理利用洋葱模型,将错误处理中间件放在最前面,这样内层中间件的错误会向外传递,被错误处理中间件捕获。
1. 基本错误处理
创建 error-demo.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const Koa = require('koa'); const app = new Koa();
app.use(async (ctx, next) => { try { await next(); if (ctx.status === 404) { ctx.body = { code: 404, message: '页面不存在' }; } } catch (err) { ctx.status = err.status || 500; ctx.body = { code: ctx.status, message: err.message || '服务器内部错误' }; ctx.app.emit('error', err, ctx); } });
app.use(async (ctx) => { ctx.throw(400, '参数错误'); });
app.on('error', (err, ctx) => { console.error('服务器错误:', err); });
app.listen(3000);
|
八、数据库连接:以 MongoDB 为例
Koa2 可以配合各种数据库,这里以 MongoDB + Mongoose 为例。
1. 安装依赖
1
| npm install mongoose --save
|
2. 连接数据库
创建 db.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/koa2-demo', { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection; db.on('error', console.error.bind(console, 'MongoDB 连接错误:')); db.once('open', () => { console.log('MongoDB 连接成功!'); });
module.exports = mongoose;
|
3. 定义 Schema 和 Model
创建 models/User.js:
1 2 3 4 5 6 7 8 9 10 11
| const mongoose = require('../db');
const UserSchema = new mongoose.Schema({ name: { type: String, required: true }, age: { type: Number, default: 18 }, email: String });
module.exports = mongoose.model('User', UserSchema);
|
4. 在路由中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const Koa = require('koa'); const Router = require('koa-router'); const bodyParser = require('koa-bodyparser'); const User = require('./models/User');
const app = new Koa(); const router = new Router();
app.use(bodyParser());
router.post('/user', async (ctx) => { const { name, age, email } = ctx.request.body; const user = new User({ name, age, email }); await user.save(); ctx.body = { code: 200, message: '创建成功', data: user }; });
router.get('/user', async (ctx) => { const users = await User.find(); ctx.body = { code: 200, data: users }; });
app.use(router.routes()).use(router.allowedMethods()); app.listen(3000);
|
九、日志:记录请求与错误
1. 开发环境:koa-logger
简单的请求日志,开发时用:
1
| npm install koa-logger --save
|
1 2
| const logger = require('koa-logger'); app.use(logger());
|
2. 生产环境:winston
更强大的日志库,支持分级、文件存储:
1
| npm install winston --save
|
创建 utils/logger.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const winston = require('winston');
const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] });
if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() })); }
module.exports = logger;
|
十、安全:常见安全防护
1. 设置安全头:koa-helmet
1
| npm install koa-helmet --save
|
1 2
| const helmet = require('koa-helmet'); app.use(helmet());
|
2. 防 CSRF:koa-csrf
1
| npm install koa-csrf koa-session --save
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const session = require('koa-session'); const CSRF = require('koa-csrf');
app.keys = ['secret key']; app.use(session({ key: 'koa:sess', maxAge: 86400000 }, app));
app.use(new CSRF());
router.get('/form', async (ctx) => { await ctx.render('form', { csrf: ctx.csrf }); });
|
3. 输入验证:Joi
防止 SQL 注入、XSS,先验证输入:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Joi = require('joi');
const schema = Joi.object({ name: Joi.string().required().min(2).max(20), age: Joi.number().integer().min(1).max(120) });
const { error, value } = schema.validate(ctx.request.body); if (error) { ctx.throw(400, error.details[0].message); }
|
十一、部署:生产环境部署
1. 使用 PM2 进程管理
PM2 可以让应用在后台运行,自动重启,负载均衡。
启动应用:
1
| pm2 start app.js --name koa2-demo
|
常用命令:
1 2 3 4 5
| pm2 list pm2 logs koa2-demo pm2 restart koa2-demo pm2 stop koa2-demo pm2 delete koa2-demo
|
2. 配置 PM2 配置文件
创建 ecosystem.config.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| module.exports = { apps: [{ name: 'koa2-demo', script: './app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' } }] };
|
启动:
1
| pm2 start ecosystem.config.js --env production
|
十二、进阶:自定义中间件开发
Koa2 的中间件开发非常简单,就是一个返回 async (ctx, next) => {} 的函数。
示例:响应时间中间件
1 2 3 4 5 6 7 8 9 10 11 12 13
| const responseTime = () => { return async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`); console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }; };
app.use(responseTime());
|
十三、Koa2 vs Express:简单对比
| 特性 |
Koa2 |
Express |
| 异步处理 |
async/await,无回调地狱 |
回调函数或 Promise |
| 中间件模型 |
洋葱模型,更灵活 |
线性模型 |
| 核心体积 |
极轻量(~1500行),无内置功能 |
较重,内置路由、静态资源等 |
| 扩展性 |
完全靠中间件扩展,灵活 |
内置功能多,开箱即用 |
| 学习曲线 |
稍高(需理解洋葱模型) |
较低(开箱即用) |
十四、总结
Koa2 是一个非常优秀的 Node.js Web 框架,它的轻量、灵活、洋葱模型、async/await 是最大的优势。掌握本文的内容,已经可以用 Koa2 开发一个完整的 Web 应用了。