Featured image of post Koa

Koa

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.requestKoa Request 对象。(推荐使用),包含请求相关属性(如 url、method 等)`const url = ctx.request.url; // 获取请求 URL```
上下文对象ctx.responseKoa Response 对象。(推荐使用),包含响应相关属性(如 body、status 等)ctx.response.status = 200; // 设置响应状态码
上下文对象ctx.reqNode 的 request 对象。(不推荐直接使用)const rawReq = ctx.req;
上下文对象ctx.resNode 的 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)重定向请求,默认状态码 302ctx.redirect('/login');
上下文对象ctx.app.emit使用由第一个参数定义的类型发出事件
中间件流程next()执行下一个中间件(必须 await),实现中间件的洋葱模型app.use(async (ctx, next) => { console.log('前'); await next(); console.log('后'); });

ctx.app.emit

使用方法和注意事项

  1. emit 的参数格式:ctx.app.emit(事件名, 参数1, 参数2, ...)
  • 第一个参数必须是事件名(字符串)

  • 后面的参数是传递给事件处理函数的参数(可以传多个,比如 err、ctx、user 等);

  1. 事件监听的顺序:app.on(事件名, 处理函数) 要写在 emit 之前,否则触发事件时没有处理函数,事件会 “丢失”;

  2. 内置事件 vs 自定义事件:

  • KOA 内置事件:主要是 error(最常用),其他还有 listening(服务启动时触发)等;
  • 自定义事件:你可以随便命名(比如 user_loginorder_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.silenttrue,否则将所有错误输出到 stderr【 错误输出控制台 】。

  • app.silent = false(默认值):框架会把未被捕获的错误,自动打印到 stderr ;
  • app.silent = true:框架完全「静默」,不再主动输出任何错误到终端,所有错误处理都交给你的自定义逻辑。

2、 当 err.status404err.exposetrue 时,默认错误处理程序也不会输出错误。

  • 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');
});
Licensed under CC BY-NC-SA 4.0