Koa2

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 版本:

1
node -v

二、快速上手:5分钟写出第一个 Koa2 应用

1. 初始化项目

1
2
mkdir koa2-demo && cd koa2-demo
npm init -y # 快速生成 package.json

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
// 1. 引入 Koa
const Koa = require('koa');

// 2. 实例化 Koa 应用
const app = new Koa();

// 3. 编写一个简单的中间件
app.use(async (ctx) => {
// ctx 是 Koa 的上下文对象,封装了 request 和 response
ctx.body = 'Hello, Koa2!';
});

// 4. 监听端口
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});

4. 启动应用

1
node app.js

打开浏览器访问 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();

// 中间件1:最外层
app.use(async (ctx, next) => {
console.log('1. 进入中间件1');
await next(); // 调用 next() 进入下一个中间件
console.log('5. 返回中间件1');
});

// 中间件2
app.use(async (ctx, next) => {
console.log('2. 进入中间件2');
await next();
console.log('4. 返回中间件2');
});

// 中间件3:最内层
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 的 requestresponse,提供了更简洁的 API。

常用属性与方法

属性/方法 说明 示例
ctx.request Koa 的 Request 对象 ctx.request.methodctx.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(); // 实例化路由

// 1. GET 请求
router.get('/', async (ctx) => {
ctx.body = '首页';
});

// 2. GET 请求带查询参数:/user?name=koa
router.get('/user', async (ctx) => {
const name = ctx.query.name || '访客';
ctx.body = `用户:${name}`;
});

// 3. 动态路由:/user/123
router.get('/user/:id', async (ctx) => {
const id = ctx.params.id;
ctx.body = `用户ID:${id}`;
});

// 4. POST 请求(需配合 koa-bodyparser 解析请求体)
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' });
// 此时路由变成 /api/、/api/user

五、模板引擎:渲染动态 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();

// 配置 koa-views
app.use(views(path.join(__dirname, 'views'), {
extension: 'ejs' // 模板文件后缀
}));

// 渲染模板
app.use(async (ctx) => {
// 渲染 views/index.ejs,传递数据
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();

// 1. 错误处理中间件(必须放在最前面)
app.use(async (ctx, next) => {
try {
await next();
// 处理 404
if (ctx.status === 404) {
ctx.body = { code: 404, message: '页面不存在' };
}
} catch (err) {
// 捕获错误,统一返回
ctx.status = err.status || 500;
ctx.body = {
code: ctx.status,
message: err.message || '服务器内部错误'
};
// 触发 app 的 error 事件
ctx.app.emit('error', err, ctx);
}
});

// 2. 模拟错误的中间件
app.use(async (ctx) => {
// 手动抛出错误
ctx.throw(400, '参数错误');
// 或者直接 throw
// throw new Error('服务器错误');
});

// 3. 监听 error 事件,打印日志
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');

// 连接 MongoDB
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');

// 定义 Schema
const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, default: 18 },
email: String
});

// 导出 Model
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');

// 配置 session
app.keys = ['secret key'];
app.use(session({
key: 'koa:sess',
maxAge: 86400000
}, app));

// 配置 CSRF
app.use(new CSRF());

// 在路由中传递 CSRF token 给前端
router.get('/form', async (ctx) => {
await ctx.render('form', { csrf: ctx.csrf });
});

3. 输入验证:Joi

防止 SQL 注入、XSS,先验证输入:

1
npm install joi --save
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
npm install pm2 -g  # 全局安装

启动应用:

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', // 利用多核 CPU
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 应用了。


Koa2
https://cszy.top/2017-07-17 koa2/
作者
csorz
发布于
2017年7月17日
许可协议