说明
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