Koa 框架使用步骤
Koa 是一个轻量级的 Node.js Web 框架,由 Express 原班人马打造,基于异步中间件机制,适合构建高效的服务端应用。以下是使用 Koa 框架的基本步骤:
1. 初始化项目
mkdir koa-demo
cd koa-demo
npm init -y
2. 安装 Koa
npm install koa
3. 创建基础服务
在项目根目录创建 app.js 文件:
// 创建一个 Koa 应用
const Koa = require('koa');
const app = new Koa();
// 添加中间件
app.use(async (ctx) => {
ctx.body = 'Hello Koa!'; // 返回响应
});
// 启动服务
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
4. 运行服务
node app.js
访问 http://localhost:3000 即可看到 Hello Koa!。
核心中间件
路由处理
安装官方推荐的 koa-router:
npm install koa-router
示例代码:
const Router = require('koa-router');
const router = new Router();
router.get('/about', (ctx) => {
ctx.body = 'About Page';
});
app.use(router.routes());
解析请求体
安装 koa-bodyparser:
npm install koa-bodyparser
示例代码:
const bodyParser = require('koa-bodyparser');
app.use(bodyParser()); // 解析 POST 请求的 JSON 或 Form 数据
静态文件服务
安装 koa-static:
npm install koa-static
示例代码:
const static = require('koa-static');
app.use(static('public')); // 托管 public 目录下的文件
错误处理
通过中间件捕获错误:
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = 500;
ctx.body = { error: err.message };
}
});
级联
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
API
| 分类 | API 名称 | 作用说明 | 使用示例 |
|---|---|---|---|
| 应用实例 | new Koa() | 创建 KOA 应用实例,是所有功能的入口 | const Koa = require('koa'); const app = new Koa(); |
| 应用实例 | app.listen([port[, host]]) | 启动 HTTP 服务器,监听指定端口和主机,返回 http.Server 实例 | app.listen(3000, '127.0.0.1', () => console.log('服务启动在3000端口')); |
| 应用实例 | app.use(middleware) | 注册中间件(核心),中间件会按注册顺序执行 | app.use(async (ctx, next) => { await next(); ctx.body = 'Hello KOA'; }); |
| 应用实例 | app.context | 扩展 ctx(上下文)对象的原型,可添加全局通用属性/方法 | app.context.db = () => console.log('连接数据库'); |
| 应用实例 | app.keys | 设置 cookie 签名密钥(数组形式),用于 ctx.cookies 签名 | app.keys = ['secretKey1', 'secretKey2']; |
| 上下文对象 | ctx | 每个请求的上下文对象,整合 req/res,是中间件的核心参数 | app.use(async (ctx) => { ctx.body = '请求上下文'; }); |
| 上下文对象 | ctx.request | Koa Request 对象。(推荐使用),包含请求相关属性(如 url、method 等) | `const url = ctx.request.url; // 获取请求 URL``` |
| 上下文对象 | ctx.response | Koa Response 对象。(推荐使用),包含响应相关属性(如 body、status 等) | ctx.response.status = 200; // 设置响应状态码 |
| 上下文对象 | ctx.req | Node 的 request 对象。(不推荐直接使用) | const rawReq = ctx.req; |
| 上下文对象 | ctx.res | Node 的 response 对象。(不推荐直接使用) | const rawRes = ctx.res; |
| 上下文对象 | ctx.body | 快捷设置响应体(等价于 ctx.response.body) | ctx.body = { code: 200, data: 'success' }; |
| 上下文对象 | ctx.status | 快捷设置响应状态码(等价于 ctx.response.status) | ctx.status = 404; |
| 上下文对象 | ctx.state | 推荐的命名空间,用于通过中间件和前端视图传递信息 | ctx.state.user = await User.find(id); |
| 上下文对象 | ctx.cookies.get(name) | 获取客户端 cookie,支持签名验证 | const token = ctx.cookies.get('token', { signed: true }); |
| 上下文对象 | ctx.cookies.set(name, value, options) | 设置客户端 cookie,可配置过期时间、签名等 | ctx.cookies.set('token', '123456', { maxAge: 86400000, signed: true }); |
| 上下文对象 | ctx.throw([status], [msg]) | 抛出 HTTP 异常,自动设置状态码和响应体 | ctx.throw(403, '禁止访问'); |
| 上下文对象 | ctx.redirect(url) | 重定向请求,默认状态码 302 | ctx.redirect('/login'); |
| 上下文对象 | ctx.app.emit | 使用由第一个参数定义的类型发出事件 | |
| 中间件流程 | next() | 执行下一个中间件(必须 await),实现中间件的洋葱模型 | app.use(async (ctx, next) => { console.log('前'); await next(); console.log('后'); }); |
ctx.app.emit
使用方法和注意事项
emit的参数格式:ctx.app.emit(事件名, 参数1, 参数2, ...)
第一个参数必须是事件名(字符串);
后面的参数是传递给事件处理函数的参数(可以传多个,比如 err、ctx、user 等);
事件监听的顺序:
app.on(事件名, 处理函数)要写在emit之前,否则触发事件时没有处理函数,事件会 “丢失”;内置事件 vs 自定义事件:
- KOA 内置事件:主要是
error(最常用),其他还有listening(服务启动时触发)等; - 自定义事件:你可以随便命名(比如
user_login、order_create),完全根据业务需求来;
处理 KOA 内置的 error 事件
const Koa = require('koa');
const app = new Koa();
// 第一步:监听 error 事件(处理事件)
// app.on(事件名, 处理函数):监听自定义事件
app.on('error', (err, ctx) => {
// 这里可以统一处理错误:记录日志、发送告警、统计错误率等
console.error(`[全局错误处理] 路径:${ctx.url},错误:${err.message}`);
// 比如写入日志文件、发送钉钉/微信告警等
});
// 第二步:在中间件中触发 error 事件(触发事件)
app.use(async (ctx) => {
try {
// 模拟业务逻辑出错
const num = undefined;
ctx.body = num.toString(); // 会报错:Cannot read property 'toString' of undefined
} catch (err) {
// 触发 error 事件,把错误和 ctx 传过去
ctx.app.emit('error', err, ctx);
// 给客户端返回友好响应
ctx.status = 500;
ctx.body = { code: 500, msg: '服务器内部错误' };
}
});
app.listen(3000, () => {
console.log('服务启动在 http://localhost:3000');
});
自定义事件
const Koa = require('koa');
const app = new Koa();
// 第一步:监听自定义事件(比如 user_login)
app.on('user_login', (user, ctx) => {
// 处理逻辑:记录登录日志、更新最后登录时间等
console.log(`用户【${user.name}】(${user.id}) 登录了,IP:${ctx.ip},时间:${new Date()}`);
// 实际开发中可以写入数据库/日志文件
});
// 第二步:在业务中间件中触发自定义事件
app.use(async (ctx) => {
// 模拟用户登录逻辑
const user = { id: 1001, name: '张三' };
ctx.state.user = user;
// 触发 user_login 事件,传递用户信息和 ctx
ctx.app.emit('user_login', user, ctx);
ctx.body = { code: 200, msg: '登录成功', data: user };
});
app.listen(3000);
错误处理
1、 默认情况下,除非 app.silent 为 true,否则将所有错误输出到 stderr【 错误输出控制台 】。
app.silent = false(默认值):框架会把未被捕获的错误,自动打印到 stderr ;app.silent = true:框架完全「静默」,不再主动输出任何错误到终端,所有错误处理都交给你的自定义逻辑。
2、 当 err.status 为 404 或 err.expose 为 true 时,默认错误处理程序也不会输出错误。
err.expose:为true,是把错误暴露给客户端,而不会输出到 stderr。(如需要打印到 stderr,需自定义)
3、 执行自定义错误处理逻辑(例如集中式日志记录),需要添加“error”事件侦听器
app.on('error', err => {
log.error('server error', err)
});
4、 如果在 req/res 周期中出现错误,并且无法响应客户端,则还会传递 Context 实例
app.on('error', (err, ctx) => {
log.error('server error', err, ctx)
});
注意:
如需自定义错误中间件,需放在中间件的最上面, 并通过
await next()包裹所有后续逻辑,才能全局捕获错误app.on('error')的位置只要在app.listen()之前即可(前 / 后都可以),核心是先注册、后触发。中间件负责给用户返回响应,
app.on('error')负责后台记录日志 / 告警。
示例:
const Koa = require('koa');
const app = new Koa();
// 【推荐写法】1. 先定义全局 error 监听(位置可前可后,只要在 listen 前)
app.on('error', (err, ctx) => {
// 这里是「全局错误日志/告警」的统一出口,只做记录,不返回响应
console.error(`[全局错误] 路径:${ctx?.url || '未知'},错误:${err.message}`);
});
// 2. 错误捕获中间件(必须是第一个 app.use!)
app.use(async (ctx, next) => {
try {
// 执行后续所有中间件
await next();
} catch (err) {
// ① 给客户端返回友好响应(这是用户能看到的)
ctx.status = err.status || 500; // 优先用错误自带的状态码,没有则 500
ctx.body = {
code: ctx.status,
msg: err.status ? err.message : '服务器内部错误' // 4xx 返原始信息,5xx 隐藏详情
};
// ② 触发全局 error 事件(把错误传给上面的监听函数)
ctx.app.emit('error', err, ctx);
}
});
// 3. 业务中间件(模拟各种业务逻辑)
app.use(async (ctx) => {
// 模拟错误场景1:主动抛错(带状态码)
// if (ctx.url === '/404') throw { status: 404, message: '资源不存在' };
// 模拟错误场景2:代码运行时错误(无状态码)
const num = undefined;
ctx.body = num.toString(); // 会触发 TypeError
});
app.listen(3000, () => {
console.log('服务启动在 http://localhost:3000');
});
