什么是koa?
koa是Express的下一代基于Node.js的web框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用和API变得得心应手。
Koa能干什么?
主要用途
- 网站(比如cnode这样的论坛)
- api(三端:pc、移动端、h5)
- 与其他模块搭配,比如和socket.io搭配写弹幕、im(即时聊天)等
koa是微型web框架,但它也是个Node.js模块,也就是说我们也可以利用它做一些http相关的事儿。举例:实现类似于http-server这样的功能,在vue或react开发里,在爬虫里,利用路由触发爬虫任务等。比如在bin模块里,集成koa模块,启动个static-http-server这样的功能,都是非常容易的。
搭建项目启动服务
1 | // 1. 创建项目文件夹后初始化npm |
生成目录结构如下:
编写app/index.js
1 | const Koa = require('koa'); |
根目录下运行node app/index.js
,启动成功后控制台出现API server listening on 0.0.0.0:3333
,打开浏览器访问本机ip:3333
路由处理
koa
中处理相应的路由返回对应的响应这一开发过程类似java
中编写controller
,restful
风格的路由可以非常语义化的根据业务场景编写对应的处理函数,前端利用axios
访问服务端找到对应的函数(路由名字)来获取对应想要的结果。
编写app/index.js
:
1 | // app/index.js |
注意:每次在koa中更新代码后想要生效必须重启koa服务
这时,我们再访问试试:
结果按我们预期返回了,这时我们先解决上诉的问题,如何热更新代码来帮助我们提高开发效率
安装
nodemon
1
2npm install nodemon
npm i nodemon -g // 建议直接全局安装
- 修改
package.js
1 | "scripts": { |
运行npm run start
再次启动服务,这时修改代码后只需要刷新浏览器即可,不用重启node服务了!
可以预见的是:上面对路由的处理在实战中是不可行的,api逐渐增加后需要考虑到系统的可维护性,koa-router
应运而生。
koa-router: 集中处理URL的中间件,它负责处理URL映射,根据不同的URL调用不同的处理函数,这样,我们就能能专心为每个URL编写处理函数
在 app
目录下新建 router
目录,如下所示:
安装koa-router
1 | npm install koa-router |
编写app/router/index.js
1 | const koaRouter = require('koa-router'); |
浏览器中再次访问测试,http://192.168.0.197:3333/test3
,返回response for test3
,返回结果与之前一致。再次细想一下,实际公司的业务场景中,router/index.js
中可能一个处理函数就会非常庞大,因此,路由文件我们只需要关心具体的路由,它对应的处理函数可以单独提出来统一管理。我们可以把业务逻辑处理函数放到controller
中,如下:
我们新增了三个文件:
app/router/routes.js
路由列表文件app/contronllers/index.js
业务处理统一导出app/contronllers/test.js
业务处理文件
所有的业务逻辑代码放到controller中管理,如app/contronllers/test.js
所示:
1 | const echo = async ctx => { |
app/contronllers/index.js
统一入口,管理导出
1 | const test = require('./test'); |
app/router/routes.js
路由文件专心管理所有路由,无需维护对应业务逻辑代码
1 | const { test } = require('../controllers'); |
改造app/router/index.js
1 | const koaRouter = require('koa-router'); |
打开浏览器访问http://192.168.0.197:3333/test1
结果如逾期正常返回,测试成功。
参数解析
测试完get请求后再请求一个post请求,path为/postTest
,参数为name: wangcong
,请求如下:
打印出console.log('postTest', ctx)
如下,好像并没有找到我们传入的参数’name’,那如何获取到post的请求体呢?
koa-bodyparser
: 对于POST请求的处理,koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中
安装中间件之前,我们可以按照改造router的方式改造一下中间件的管理
新建app/midllewares
目录,添加index.js
文件统一管理所有中间件
1 | const router = require('../router'); |
index.js
文件里集中了所有用到的中间件,接下来改造下启动文件 app/index.js
:
1 | const Koa = require('koa'); |
compose
是一个工具函数,Koa.js
的中间件通过这个工具函数组合后,按 app.use()
的顺序同步执行,也就是形成了 洋葱圈 式的调用。
引入 koa-bodyparser
统一处理请求参数,注意:bodyParser 为了处理每个 Request 中的信息,要放到路由前面先让他处理再进路由
1 | // midllewares/index.js |
postman请求测试
获取ctx.request.body
成功!
引用koa-bodyparser
文档的一句话,可以看出来它并不支持二进制流来进行上传,并且希望我们用co-busboy
来解析multipart format data
Notice: this module don’t support parsing multipart format data, please use co-busboy to parse multipart format data.
替换koa-bodyparser
为koa-body
,koa-body
主要是下面两个依赖:
1 | "co-body": "^5.1.1", |
官方这样对它做了介绍
A full-featured
koa
body parser middleware. Supportsmultipart
,urlencoded
, andjson
request bodies. Provides the same functionality as Express’s bodyParser -multer
.
修改app/midllewares/index.js
:
1 | const { tempFilePath } = require('../config'); |
其中,创建了config和utils两个文件夹,各自目录分别为:
config
中文件目前只配置了上传文件的临时路径,后面还可以配置一些不同环境下的配置相关:
utils
文件夹下创建了一个file.js
工具文件和`index.js统一导出文件,主要处理对文件相关(路径、文件名等)的逻辑:
1 | // utils/file.js |
1 | // utils/index.js |
在app/index.js
引入全局公共部分,挂载到app.context
上下文中:
1 | // app/index.js |
测试上传功能:
注意:KoaBody配置中,keepExtensions: true必须开启,否则上传不会成功!
查看app目录下,生成了我们刚刚上传的文件
在koa-body @4中,控制台打印文件相关信息用
ctx.request.files
,低版本使用ctx.request.body.files
统一响应体 & 错误处理
统一 格式处理返回响应,可以充分利用洋葱模型进行传递,我们可以编写两个中间件,一个统一返回格式middleware,一个错误处理middleware,分别如下:
文件app/midllewares/response.js
1 | const response = () => { |
文件 app/middlewares/error.js
1 | const error = () => { |
app/middlewares/index.js
引用它们:
1 | const { tempFilePath } = require('../config'); |
再次强调一遍,app.use()中,中间件执行是按续同步执行,mdResHandler定义了两种处理通道(成功和失败),真正判断逻辑在error.js中间件中,一种是业务型错误码,需要返回给前端进行处理,另一种是服务端代码运行时报错,这种错误类型我们需要出发koa的错误处理事件去处理。error.js中判断处理后都是调用mdResHandler统一返回格式返回请求响应。针对服务端运行时代码错误,我们还需要做出修改,在app/index.js
中修改代码如下:
1 | app.on('error', (err, ctx) => { |
完成后,我们还是利用之前的controller/ap/test.js
中echo的代码:
1 | const echo = async ctx => { |
再次请求看看跟之前有什么不一样
结果如逾期返回,再进行模拟错误的返回,修改test.js
下的echo
函数如下:
1 | // test.js |
postman再次请求测试:
结果如预期返回
修改test.js
为koa运行时的代码错误:
1 | const echo = async ctx => { |
再次请求,得到结果如下:
至此,错误处理搞定了,统一返回格式也搞定。
参数校验
参数校验可以极大的避免上诉的程序运行时的错误,在这个例子里,我们也将参数校验放在controller
里面去完成,test.js
新增一个业务处理函数print
用于返回前端姓名,打印在页面上:
1 | const print = async ctx => { |
请求测试,正常传参如下 :
不传参数,返回错误状态码10001
:
可以预料的是,随着业务场景复杂度的上升,controller层后面对于参数校验的部分代码会变得越来越庞大,所以这部分一定是可以优化的,第三方插件 joi
就是应对这种场景,我们可以借助此中间件帮助我们完成参数校验。在 app/middlewares/
下添加 validator.js
文件:
1 | module.exports = paramSchema => { |
修改app/router/index.js
:
1 | const koaRouter = require('koa-router'); |
可以看到,route
中多解构了一个valid
来作为validator
的参数,app/router/routes.js
中print
路由新增一条校验规则,如下:
1 | { |
koa-router
允许添加多个路由级中间件,我们将参数校验放在这里处理。随后在 app
目录下新建目录 schema
,用来存放参数校验部分的代码,添加两个文件:
app/schema/index.js
:1
2
3
4
5const schTest = require('./test');
module.exports = {
schTest
};app/schema/test.js
:1
2
3
4
5
6
7
8
9
10
11
12const Joi = require('@hapi/joi');
const print = {
query: Joi.object({
name: Joi.string().required(),
age: Joi.number().required()
})
};
module.exports = {
list
};把之前
app/controller/test.js
手动校验部分删掉 ,测试joi
中间件是否生效:
1 | const print = async ctx => { |
请求接口测试
到这里,参数校验就算整合完成,joi
更多的使用方法请查看文档
配置跨域
使用@koa/cors
插件来进行跨域配置,app/middlewares/index.js
添加配置,如下:
1 | // ...省略其他配置 |
日志
采用 log4js
来记录请求日志,添加文件 app/middlewares/log.js
:
1 | const log4js = require('log4js'); |
在 app/middlewares/index.js
中引入上面写的日志中间件:
1 | const log = require('./log'); // 添加日志 |
利用postman请求接口测试效果:
打开日志文件,查看日志 :
1 | [2021-06-27T17:45:53.803] [INFO] default - {"method":"GET","path":"/test1","origin":"http://192.168.0.197:3333","query":{},"body":{},"ip":"192.168.0.197","headers":{"host":"192.168.0.197:3333","connection":"keep-alive","cache-control":"no-cache","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36","postman-token":"ae200806-a92b-00c4-5f2d-d5afdb7d717c","accept":"*/*","accept-encoding":"gzip, deflate","accept-language":"zh-CN,zh;q=0.9,en;q=0.8"},"status":200,"params":{},"result":{"code":0,"data":"这是一段文字...","msg":"success"}} |
到这里,日志模块引用成功!
数据库操作
在app
下再新增一个 service
目录, 之后的数据库操作放在 service
目录下,controller
专注业务处理,service
专注数据库的增删改查等事务操作。还可以添加一个 model 目录,用来定义数据库表结构,具体的操作将在之后的koa应用实战中具体展示。
总结
基本的koa实战型项目到这里就结束了,企业级开发中,还会有更多的问题需要解决,期待更加贴近企业级的实战项目。
- 本文链接:https://cong1223.github.io/2021/06/28/koa%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub IssuesGitHub Discussions