Koa2

简介

  • 官网
  • koa2是express同一个团队开发的一个新的Web框架;
  • 相对于express具有更强的异步处理能力;**(Koa框架的next()函数返回的是一个promise)**
  • Koa2的核心代码只有1600+行,是一个更加轻量级的框架,我们可以根据需要安装和使用中间件;

使用

  • node下载koa模块
  • 命令:
    • 初始化服务:npm init -y
    • 下载koa:npm i koa

koa2初体验

PS:我一直以为是koa,然后群友说koa2,我才发现我用的是koa2;

Koa vs Express && Koa1 vs Koa2什么是KOA2?KOA2和KOA的差别

  • koa2并没有像express一样,将req和res分开,而是将它们作为 ctx的属性;
  • ctx代表依次请求的上下文对象;
  • ctx.request:获取请求对象;(koa封装的请求对象)
  • ctx.response:获取响应对象;(koa封装的响应对象)
  • ctx.req:(Node封装的请求对象)
  • ctx.res:(Node封装的响应对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 导出的是个类,所以K大写
const Koa = require("koa");

// 因为是个类,所以需要实例化
const app = new Koa();

// koa所有中间件执行完,没有返回结果res.end(),默认返回 NOT FOUND; express则会一直pending;
// 与express相比,koa两个参数,第一个是context,表示上下文,缩写ctx;
app.use((ctx, next) => {
// context有 request与response
// ctx.request;
// ctx.response;

// koa不是res.end("xxx")返回了,而是ctx.response.body=xxx;(简写ctx.body=xxx) xxx可以为 string,json,array,buffer...
ctx.body = "hello world";
});

// 监听
app.listen(3000, () => {
console.log("server is running port is 3000");
});

koa2中间件

koa注册中间件没有methods方式,只有use;且use第一个参数不能设置为路径;且不能连续注册中间件;

  • 即:没有提供以下的注册方式:
    • methods方式:app.get()/.post()
    • 第一个参数为路径:app.use(“/home”,(ctx,next)=>{})
    • 连续注册中间件:app.use((ctx,next)=>{},(ctx,next)=>{},(ctx,next)=>{})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 导出的是个类,所以K大写
const Koa = require("koa");

// 因为是个类,所以需要实例化
const app = new Koa();


/**
* koa注册中间件没有methods方式,只有use,而且use第一个参数不能设置为路径;而且不能连续注册中间件;
* 即:没有提供以下的注册方式
* methods方式: app.get()/.post()
* 第一个参数为路径:app.use("/home",(ctx,next)=>{})
* 连续注册中间件: app.use((ctx,next)=>{},(ctx,next)=>{},(ctx,next)=>{})
*/

app.use((ctx, next) => {
ctx.response.body = "hello world";
if (ctx.request.url == "/login") {
if (ctx.request.method == "GET") {
ctx.response.body = "login success"
}
} else {
ctx.response.body = "login flase"
}

})

// 监听
app.listen(3000, () => {
console.log("server is running port is 3000");
})

不难发现,上面请求方式,路径之类的都需要我们自己来做判断,写法太复杂了,那如何简单化呢?答案是使用koa的路由。

koa2路由

express中,router是集成在框架中的;由于koa是个轻量级的框架,所以没有集成路由,我们需要安装第三方库npm i koa-router;路由里面有 get post put......

  • 我们可以先封装一个 router的 user.js 的文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//新建一个router文件夹,下面创建user.js
const Router = require("koa-router");
// 同koa一样 引入的是个类,需要实例化;
const router = new Router({prefix:"/users"}); //perfix就是接口的前缀;

router.get("/",(ctx,next)=>{
ctx.response.body="user lists~"
})

router.put("/",(ctx,next)=>{
ctx.response.body="put request~"
})

module.exports=router;
  • 在app中引入 user.js文件,然后通过app.use()将其注册为中间件;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在app文件里引入路由
// 导出的是个类,所以K大写
const Koa = require("koa");

// 因为是个类,所以需要实例化
const app =new Koa();

// koa很轻量,使用路由需要下载第三方库: yarn add koa-router; 路由里面有 get post put......
// 引入路由
const userRouter = require("./router/user.js");
app.use(userRouter.routes());

// 现在一直返回的NOT FOUND ,我没有写post,更希望返回的是 Method Not Allowed 状态码:405;
app.use(userRouter.allowedMethods());

// 监听
app.listen(3000,()=>{
console.log("server is running port is 3000");
})

POST请求body参数解析

中间件应用 - json-urlencoded 解析

express中,body-parser是集成在框架中的;由于koa是个轻量级的框架,所以没有集成路由,我们需要安装第三方库npm i koa-bodyparser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 导出的是个类,所以K大写
const Koa = require("koa");
// 因为是个类,所以需要实例化
const app =new Koa();

// express是内置的body-parser ;koa没有内置,需要下载: yarn add koa-bodyparser
const bodyparser = require("koa-bodyparser");
app.use(bodyparser());

app.use((ctx,next)=>{
console.log(ctx.request.body); //json和 urlencoded都可以解析;
ctx.response.body="POST请求~~";
})

app.listen(3000,()=>{
console.log("server is running port is 3000");
})

中间件应用 - form-data 解析

  • 注:此章节更多细节看express文章;

form-data 解析:一般用于上传图片文件;

express一样,框架没有内置解析form-data的库,需要下载:npm i koa-multer

初步使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 导出的是个类,所以K大写
const Koa = require("koa");
// 因为是个类,所以需要实例化
const app =new Koa();

const Router = require("koa-router");


// form-data无法获取,需要下载: yarn add koa-multer;
const multer = require("koa-multer");
const upload =multer();
app.use(upload.any()); //不建议全局 router可以连续注册中间件,可以放在后面; Router.post("/login",upload.any(),(ctx,next)=>{});

app.use((ctx,next)=>{
console.log(ctx.req.body); // multer是ctx.req,而不是ctx.request;其实form取数据基本都是文件,数据不常用;
ctx.response.body="POST请求form-data参数,不常用,基本用来传文件~";
})

app.listen(3000,()=>{
console.log("server is running port is 3000");
})

文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 跟express基本一样
const path= require("path");
const Koa =require("koa");
const Router = require("koa-router");
const multer = require("koa-multer");


const app =new Koa();
const uploadRouter = new Router({prefix:"/upload"});
app.use(uploadRouter.routes());

// 这个不会自动创建文件夹,需要手动先创建;
const storage =multer.diskStorage({
destination:(req,file,cb)=>{
cb(null,"./uploads/")
},
filename:(req,file,cb)=>{
cb(null,Date.now()+path.extname(file.originalname));
}
})
const upload =multer({
storage,
});

uploadRouter.post("/avator",upload.single("avatorss"),(ctx,next)=>{
console.log(ctx.req.file);
ctx.response.body="文件上传成功!"
})

app.listen(3000,()=>{
console.log("server is running port is 3000")
})


GET请求参数解析

params获取路由参数;request获取地址栏?之后的参数;下面是两者结合的demo;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 导出的是个类,所以K大写
const Koa = require("koa");
// 因为是个类,所以需要实例化
const app =new Koa();

const Router = require("koa-router");
const userRouter = new Router({prefix:"/users"})

app.use(userRouter.routes());
app.use(userRouter.allowedMethods());

// http://127.0.0.1:3000/users/abc?username=123&password=456
userRouter.get("/:id",(ctx,next)=>{
console.log(ctx.request.params); //{ id: 'abc' }
console.log(ctx.request.query); // [Object: null prototype] { username: '123', password: '456' }
ctx.response.body="GET请求~~";
})

app.listen(3000,()=>{
console.log("server is running port is 3000");
})

响应数据

  • 输出结果:body将响应主体设置为以下之一:
    • string :字符串数据
    • Buffer :Buffer数据
    • Stream :流数据
    • Object|| Array:对象或者数组
    • null :不输出任何内容
    • 如果response.status尚未设置,Koa会自动将状态设置为200或204。
  • ctx.response.body 可以简写为 ctx.body
  • ctx.status=xxx 设置状态码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Koa = require("koa");

const app = new Koa();

app.use((ctx, next) => {

// ctx.response.body="hello world";

// ctx.response.body={
// name:"fsllala",
// age:18
// }

// ctx.response.body=[123,123,"123ads"];

// 设置状态码
ctx.status = 400;
// ctx.response.body 可以简写为 ctx.body
ctx.body = "hahahaa";
})

app.listen(3000, () => {
console.log("Server is running port is 3000")
})

错误处理

  • 使用ctx.app.emit("error",new Error("xxxx"),ctx)传参;(第一个”error”的监听错误信息,第二个是传递的错误信息参数,第三个是ctx参数)

  • 使用app.on("error",(error,ctx)=>{})接收错误信息;(第一个”error”的监听错误信息,第二个是回调函数,接收传送来的 错误信息参数和ctx参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const Koa = require("koa");

const app = new Koa();

app.use((ctx,next)=>{
const isLogin =false;
if(!isLogin){
// 使用ctx里面的app的emit方法 发送一个事件;
ctx.app.emit("error",new Error("您还没有登录"),ctx);
}
})

// 监听错误事件
// 这个和上面的 ctx.app.emit("error",new Error("您还没有登录"),ctx)变量是一一对应的;
app.on("error",(err,ctx)=>{
ctx.status=401;
ctx.body=err.message;
})

app.listen(3000,()=>{
console.log("Server is running port is 3000")
})

Koa2与express区别

  • 从架构设计上来说:
    • express是完整和强大的,其中帮助我们内置了非常多好用的功能;
    • koa是简洁和自由的,它只包含最核心的功能,并不会对我们使用其他中间件进行任何的限制。
      • 甚至是在app中连最基本的get、post都没有给我们提供;
      • 我们需要通过自己或者路由来判断请求方式或者其他功能;
  • express和koa框架他们的核心其实都是中间件:
    • 但是他们的中间件事实上,它们的中间件的执行机制是不同的,特别是针对某个中间件中包含异步操作时;
    • 所以,接下来,我们再来研究一下express和koa中间件的执行顺序问题;

案例实现

这里通过一个需求来演示所有的过程;

  • 假如有三个中间件会在一次请求中匹配到,并且按照顺序执行;

  • 我希望最终实现的方案是:

    • 在middleware1中,在req.message中添加一个字符串 aaa; res.message="aaa";
    • 在middleware2中,在req.message中添加一个 字符串bbb; res.message+="bbb";
    • 在middleware3中,在req.message中添加一个 字符串ccc; res.message+="ccc";
    • 当所有内容添加结束后,在middleware1中,通过res返回最终的结果;aaabbbccc(必须在第一个中间件里返回)
  • 实现方案:

    • Express同步数据的实现;
    • Express异步数据的实现;
    • Koa同步数据的实现;
    • Koa异步数据的实现;**(Koa的next()函数返回的是一个promise)**

    Express同步数据的实现;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require("express");
const app = express();

const middleware1 = (req,res,next)=>{
res.message="aaa";
next(); //因为是同步代码,所以next(),会先执行middleware2
res.end(res.message); // aaabbbccc
}

const middleware2 = (req,res,next)=>{
res.message+="bbb";
next();//因为是同步代码,所以next(),会先执行middleware3
}

const middleware3 = (req,res,next)=>{
res.message+="ccc";
}

app.use(middleware1,middleware2,middleware3);

app.listen(3000,()=>{
console.log("server is running port is 3000");
})

Express异步数据的实现;这里使用axios

  • npm i axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const express = require("express");
const axios = require("axios");
const app = express();

const middleware1 = (req, res, next) => {
res.message = "aaa";
next();
// res.end(res.message); //aaabbb
}

const middleware2 = (req, res, next) => {
res.message += "bbb";
next();
}

const middleware3 = (req, res, next) => {
// 这里是异步,上面next()到这里之后,跳过异步,看异步后面的;
// 如果执行的下一个中间件是一个异步函数,那么next默认不会等到中间件的结果,就会执行下一步操作;
axios.get("https://api.vvhan.com/api/love").then(result => {
res.message += result.data;
res.end(res.message); //要是想以中间件的形式获取到完整的res.message,express比较难处理;(可以将异步以函数的形式写在middleware1里,但不是中间件了);
})

// 先看异步后面的,即看这里,这里啥也没有,就返回middleware1了;
}

app.use(middleware1, middleware2, middleware3);

app.listen(3000, () => {
console.log("server is running port is 3000");
})

Koa同步数据的实现;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const Koa =require("koa");
const app = new Koa();

const middleware1 = (ctx,next)=>{
ctx.message="aaa";
next(); //因为是同步代码,所以next(),会先执行middleware2
ctx.body=ctx.message; // aaabbbccc
}

const middleware2 = (ctx,next)=>{
ctx.message+="bbb";
next();//因为是同步代码,所以next(),会先执行middleware3
}

const middleware3 = (ctx,next)=>{
ctx.message+="ccc";
}

// koa不支持一次注册多个中间件
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);

app.listen(3000, () => {
console.log("server is running port is 3000");
})

Koa异步数据的实现;koa的next()函数返回的是一个promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Koa = require("koa");
const axios = require("axios");
const app = new Koa();

// koa的next()函数返回的是一个promise
const middleware1 = async (ctx, next) => {
ctx.message = "aaa";
await next();
ctx.body = ctx.message; //完整的ctx.message
}

const middleware2 = async (ctx, next) => {
ctx.message += "bbb";
// 如果执行的下一个中间件是一个异步函数,那么next默认不会等到中间件的结果,就会执行下一步操作;
// 如果我们希望等待下一个异步函数的执行结果,那么需要在next函数前面加await;
await next();
}

const middleware3 = async (ctx, next) => {
const result = await axios.get("https://api.vvhan.com/api/love");
ctx.message += result.data;
}

// koa不支持一次注册多个中间件
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);

app.listen(3000, () => {
console.log("server is running port is 3000");
})