Koa2 学习文档

本文档详述了Koa2的基础知识,包括Koa2的安装、中间件的工作原理、执行顺序、常见中间件的使用,以及错误处理、日志记录和路由分发等核心概念。通过实例展示了Koa2的中间件级联和执行流程,并提供了项目实战中的目录结构和错误处理策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说明

koa是express的轻量级版本。koa不绑定任何的中间件,但是依赖中间件来完成指定的任务。koa中提供了很少的API,所以koa整体上很小。
下面文档介绍了koa基础知识,并且加上了一个小例子。

注意:koa 1.x 和 koa 2.x有一些不同,下面内容都是围绕koa 2.x做介绍。


Koa安装

$ mkdir mypp && cd myapp
$ npm init 
$ npm install koa --save 

说明:如果node版本过低,可以采用node版本控制器 n 来快速切换node版本。详细看:https://github.com/tj/n

Hello World
新建一个 app.js 文件,文件内容如下:

var koa = require(‘koa’);
var app = new koa();

app.use(ctx => {
  ctx.body = ‘hello world’;
});

app.listen(3000);

输入以下指令运行程序,

$ node --harmony app.js 

说明:高版本的 node 可以不使用 –harmony 选项。

浏览器查看运行结果,http://localhost:3000

web项目目录
开发项目时,可以按照以下目录结构来进行编写代码,养成自己的风格习惯。

|- app.js               程序启动文件
|- node_modules         项目依赖库
|- public               静态资源文件
|- routes               路由文件
|- views                前台页面显示文件
|- package.json         配置信息文件

说明:以上只是一种风格习惯,可以选择使用或者不使用。


中间件

什么是中间件?实际上就是function,而函数的类型又分为异步函数和普通函数。
koa中间件的执行都是依靠generator来进行实现,中间件的组织形式是级联的方式组织起来。中间件有个统一的接口,接口的规范如下:

async function middlewareFunction(ctx, next) {
  ...
}
app.use(middlewareFunction);

上面代码 app.use(middlewareFunction), 表示使用中间件 middlewareFunction
ctx 表示 Context, next 表示下一个中间件。

级联代码

koa的中间级实际上使用了一个调用栈来进行管理,遇到await next或者yield next就暂停,开始调用下一个中间件,以下是中间件的大概形式。

.middleware1 {
  // (1) do some stuff
  .middleware2 {
    // (2) do some other stuff
    .middleware3 {
      // (3) NO next yield !
      // this.body = 'hello world'
    }
    // (4) do some other stuff later
  }
  // (5) do some stuff lastest and return
}

执行顺序

接下来看看中间件的执行顺序和过程。
这里写图片描述
看以下代码的执行流程:

var koa = require('koa');
var app = koa();

// x-response-time
app.use(async (next) => {
  // (1) 进入路由
  var start = new Date;
  await next;
  // (5) 再次进入 x-response-time 中间件,记录2次通过此中间件「穿越」的时间
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
  // (6) 返回 this.body
});

// logger
app.use(async (next) => {
  // (2) 进入 logger 中间件
  var start = new Date;
  await next;
  // (4) 再次进入 logger 中间件,记录2次通过此中间件「穿越」的时间
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});

// response
app.use(async () => {
  // (3) 进入 response 中间件,没有捕获到下一个符合条件的中间件,传递到 upstream
  this.body = 'Hello World';
});

app.listen(3000);

中间件的种类

中间件就是函数,中间件因函数的类型分为:异步和普通中间件

异步函数

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

普通函数

app.use((ctx, next) => {
  const start = Date.now();
  return next().then(() => {
    const ms = Date.now() - start;
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
  });
});

组合多个中间件

可以使用 koa-compose 模块组合多个中间件,基本用法如下:

app.use(compose([
  function *() {},
  function *() {},
  function *() {},
]));

组合之后的中间件按照什么顺序执行?实际上取决于中间件在 compose 参数的位置。

重要的中间件

koa-logger      // 一般logger放在中间件的最顶层
koa-router      // 路由
koa-onerror     // 错误信息处理
koa-session     // 存储会话所需的属性及配置信息
koa-static      // 静态文件加载
koa-body        // 报文设置

摆放顺序

koa的中间件那么多,该如何摆放?

app
.use(errorHandler())            // 错误处理
.use(logger())                  // 日志记录
.use(staticService())           // 静态文件
.use(filterRouter())            // 路由过滤
.use(router());                 // 路由

说明:以上只是给出一个大概的摆放顺序,具体还需要自己进行修改。


Context (上下文)

koa将 request 和 response 封装到一个单独的 context 对象中。
context 在每个 request 请求中创建,中间件作为接收器对其进行引用。

app.use(async ctx => {
  ctx;              // Context
  ctx.request;      // is a koa Request
  ctx.response;     // is a koa Response
});

为了方便访问,将 reques t和 response 对象中的方法委托给 ctx 。例如:ctx.path 实际上是 ctx.request.path , ctx.type 实际上是 ctx.response.type。

详细可以看官方文档: http://koa.bootcss.com/


配置文件

可以采用对象存储配置信息,使用时直接引入模块进行调用即可。

// config/development.js
module.exports = {
  env: ‘development’,       // 环境名称
  port: 3000,               // 服务器监听端口
  mongodb_url: ‘’,          // 数据库地址
  redis_url: ‘’,            // redis地址
  redis_port: ‘’,           // redis端口
}

// app.js
var config = require(‘./config/development’);
...
app.listen(config.port);

错误处理

在koa中处理错误情况,可以将中间件放在第一的位置,避免下面要执行的中间件单独处理异常。看如下代码:

app.use(async (ctx, next) => {
  try {
    await next();
  } catch(error) {
    ctx.status = error.status || 500;
    ctx.body = error.message;
    ctx.app.emit(‘error’, err, ctx);
  }
});

使用ctx.throw抛出异常。看下面示例代码:

app.use(async (ctx, next) => {
  ctx.throw(500, ‘inner server errors’);
});

app.use(async (ctx, next) => {
  throw new Error(‘error message’);
});

除了上面的几种方式外,还可以编写自己的异常类。下面就只是简单提一下:

const ApiError extends Error {
  constructor(error_name, error_code, error_message) {
    super();
    this.name = error_name;
    this.code = error_code;
    this.message = error_message;
  }
}

module.exports = ApiError;

详细请看 参考[8]。参考[10]中包含自定义异常类,整篇文章写的很好。


日志记录

koa-logger可以将日志信息打印到控制台。具体使用方法如下:

var logger = require(‘koa-logger’);
...
app.use(logger());

如何将日志信息存入文件,如何按指定格式命名日志文件,如何控制日志级别,这里可以使用log4js。log4js不仅可以将日志写入文件,也可以输出到控制台。

详细可以看 参考[10]。


路由分发

采用分离路由模式会降低代码的耦合性。
集中处理所有的路由分发,将所有的逻辑控制分开。

// routes/router.js
const router = require('koa-router')();
const productid = require('../controllers/productid');
const login = require('../controllers/login');

router
  // login
  .post('/api/sap_admin/login', login)

  // productid 
  .post('/api/sap_admin/searchproductid',   productid.searchProductId)
  .post('/api/sap_admin/createproductids', productid.createProductIds)
...

module.exports = router;

项目实战

目录的组织结构如下:

|- controllers
|    |- login.js
|    |- productid.js
|
|- node_modules
|- public
|    |- index.html
|    |- index.js
|    |- index.css
|
|- routes
|    |- routes.js
|
|- app.js
|- package.json
// app.js
var Koa = require('koa');
var router = require('./routes/router');
var serve = require('koa-static');
var koaBody = require('koa-body');

var app = new Koa();

// error handling
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    ctx.status = error.status || 500;
    ctx.body = error.message;
    ctx.app.emit('error', error, ctx);
  }
})

// static file 
app.use(serve(__dirname + '/public'));

// body parser
app.use(koaBody());

// router 
app.use(router.routes());

app.listen(8000);
// routes/routes.js
const router = require('koa-router')();
const productid = require('../controllers/productid');
const login = require('../controllers/login');

router

    // login
    .post('/api/sap_admin/login', login)

    // productid 
    .post('/api/sap_admin/searchproductid', productid.searchProductId)
    .post('/api/sap_admin/createproductids', productid.createProductIds)
    .post('/api/sap_admin/importproductids', productid.importProductIds);

module.exports = router;
// controllers/login.js
async function login(ctx) {

  const data = { 
    success: true,
    allow_items: ['device', 'alarm', 'createaccount', 'delaccount', 'createid', 'adminaccount'],
    token: 'token112344555'
  };

  console.log("Login: ", ctx.request.body);

  ctx.body = data;
}

module.exports = login;

说明:以上index.html, index.js 和 index.css是基于react,react-redux,react-router,dva,redux-saga和antd打包发布的最终文件,这里不做展示。


FAQ

Q: exports 和 module.exports的区别?
A: exports是指向module.exports的引用。
require返回的是module.exports而不是exports。
module.exports初始值为一个空对象{}。


参考

[1] koa 官网中文:
http://koa.bootcss.com/#introduction
[2] koa github:
https://github.com/koajs/koa
[3] koa-logger:
https://github.com/koajs/logger
[4] koa-router:
https://github.com/alexmingoia/koa-router
[5] koa-onerror:
https://github.com/koajs/onerror
[6] koa2, async/await:
https://zhuanlan.zhihu.com/p/26216336
[7] koa源码阅读笔记1 ~ 4:
https://segmentfault.com/a/1190000006085240
[8] koa error处理:
https://yq.aliyun.com/articles/8539?spm=5176.100240.searchblog.8.u2w8qY
[9] exports和module.exports的区别:
https://cnodejs.org/topic/5231a630101e574521e45ef8
[10] koa2 从起步到填坑:
http://www.jianshu.com/p/6b816c609669
[11] koa技术分享:
http://www.jianshu.com/p/225ff3e8fa18


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值