一、认识Koa

前面我们已经学习了express,另外一个非常流行的Node Web服务器框架就是Koa。

Koa官方的介绍:

  • koa:next generation web framework for node.js;
  • koa:node.js的下一代web框架;

koa是express同一个团队开发的一个新的Web框架:

  • 目前团队的核心开发者TJ的主要精力也在维护Koa,express已经交给团队维护了;
  • Koa旨在为Web应用程序和API提供更小、更丰富和更强大的能力;
  • 相对于express具有更强的异步处理能力(后续我们再对比);
  • Koa的核心代码只有1600+行,是一个更加轻量级的框架,我们可以根据需要安装和使用中间件;

学习了express之后,学习koa的过程是很简单的;

二、Koa初体验

npm init -y

npm i koa

1
2
3
4
5
6
7
8
9
10
11
//实际导出的是一个类 规范写法K大写
const Koa = require('koa');

const app = new Koa()
/*此时我没有返回数据
1.在原生http中 和 express中,客户端会一直请求,所以一般设置超时时间
2.但在koa中,执行完所有的中间件没有返回数据时,会返回Not Found页面
*/
app.listen(8000,() => {
console.log('服务器启动成功');
})

没有返回数据
1.在原生http中 和 express中,客户端会一直请求,所以一般设置超时时间
2.但在koa中,执行完所有的中间件没有返回数据时,会返回Not Found页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//实际导出的是一个类 规范写法K大写
const Koa = require('koa');

const app = new Koa();
/*此时我没有返回数据
1.在原生http中 和 express中,客户端会一直请求,所以一般设置超时时间
2.但在koa中,执行完所有的中间件没有返回数据时,会返回Not Found页面
*/
app.use((ctx, next) => {
ctx.response.body = 'Hello';
next();
});
app.listen(8000, () => {
console.log('服务器启动成功');
});

koa注册的中间件提供了两个参数:

  • ctx:上下文(Context)对象;

  • koa并没有像express一样,将req和res分开,而是将它们作为ctx的属性;

    • ctx代表依次请求的上下文对象;
      • ctx.request:获取请求对象;
      • ctx.response:获取响应对象;
  • next:本质上是一个dispatch,类似于之前的next; 后续学习Koa的源码,来看它是一个怎么样的函数;

三、Koa中间件

koa通过创建的app对象,注册中间件只能通过use方法:

Koa并没有提供methods的方式来注册中间件;

也没有提供path中间件来匹配路径;

但是真实开发中我们如何将路径和method分离呢?

  • 方式一:根据request自己来判断;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Koa = require('koa');

const app = new Koa();
app.use((ctx, next) => {
if (ctx.request.url === '/login') {
if (ctx.request.method === 'POST') {
ctx.response.body = 'login success';
}
} else {
ctx.response.body = 'Hello';
}
});
app.listen(8000, () => {
console.log('服务器启动成功');
});

  • 方式二:使用第三方路由中间件;
    • koa官方并没有给我们提供路由的库,我们可以选择第三方库:koa-router
    • npm install koa-router
    • 我们可以先封装一个 user.router.js 的文件:
    • 在app中将router.routes()注册为中间件:

users.js:

1
2
3
4
5
6
7
8
9
10
11
const Koa = require('koa');
const Router = require('koa-router');

const router = new Router({ prefix: '/users' });
router.get('/', (ctx, next) => {
ctx.response.body = 'get request';
});
router.put('/', (ctx, next) => {
ctx.response.body = 'put request';
});
module.exports = router;

Koa路由使用.js

1
2
3
4
5
6
7
8
9
10
11
//实际导出的是一个类 规范写法K大写
const Koa = require('koa');
const app = new Koa();
const userRouter = require('./routes/users.js');

app.use(userRouter.routes())
//方法判断
app.use(userRouter.allowedMethods())
app.listen(8000, () => {
console.log('服务器启动成功');
});
  • 注意:allowedMethods用于判断某一个method是否支持:
    • 如果请求 get,那么是正常的请求,因为我们有实现get;
    • 如果请求 put、delete、patch,那么就自动报错:Method Not Allowed,状态码:405;
    • 如果请求 link、copy、lock,那么久自动报错:Not Implemented,状态码:501

四、参数解析

params — query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();

const router = new Router({ prefix: '/users' });

app.use((ctx, next) => {
///users?name=xxx&age=20
console.log(ctx.request.url);

//undefined 这样是获取不到params的
console.log(ctx.request.params);

//[Object: null prototype] { name: 'xxx', age: '20' }
console.log(ctx.request.query);
});
app.listen(8000, () => {
console.log('服务器启动成功');
});

这样是拿不到params

需要在路由里定义,才能拿到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router({ prefix: '/users' });

router.get('/:id', (ctx, next) => {
// { id: '123' }
console.log(ctx.request.params);
//[Object: null prototype] { name: 'xxx', age: '20' }
console.log(ctx.request.query);
ctx.response.body = 'get request';
});

app.use(router.routes());
app.listen(8000, () => {
console.log('服务器启动成功');
});

json — x-www-form-urlencoded

post请求,参数为json格式

1
2
3
4
{
"title":"pc",
"price":"12345"
}

正常ctx.request.body是拿不到数据的,需要第三方库bodyparser

npm install koa-bodyparser

1
2
3
4
5
6
7
8
9
10
11
12
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyparser());
app.use((ctx, next) => {
//{ title: 'pc', price: '12345' }
console.log(ctx.request.body);
});

app.listen(8000, () => {
console.log('服务器启动成功');
});

post请求,参数为x-www-form-urlencoded格式

1
2
3
4
5
6
7
8
9
10
11
12
13
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyparser());
app.use((ctx, next) => {
//{ title: 'ipone', price: '123' }
console.log(ctx.request.body);
});

app.listen(8000, () => {
console.log('服务器启动成功');
});

form-data

解析body中的数据,我们需要使用multer

安装依赖:npm install koa-multer;

使用 multer中间件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Koa = require('koa');
const multer = require('koa-multer');
const app = new Koa();
const upload = multer()
app.use(upload.any());
app.use((ctx, next) => {
//[Object: null prototype] { file: 'ipone' }
//注意这里是ctx.req 不是ctx.request
console.log(ctx.req.body);
});

app.listen(8000, () => {
console.log('服务器启动成功');
});

上传文件

参考express。

五、数据响应

body将响应主体设置为以下之一:

  • string :字符串数据
  • Buffer :Buffer数据
  • Stream :流数据
  • Object|| Array:对象或者数组
  • null :不输出任何内容
  • 如果response.status尚未设置,Koa会自动将状态设置为200或204。

请求状态:status

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Koa = require('koa');
const multer = require('koa-multer');
const app = new Koa();
const upload = multer()
app.use(upload.any());
app.use((ctx, next) => {
//[Object: null prototype] { file: 'ipone' }
//注意这里是ctx.req 不是ctx.request
console.log(ctx.req.body);
});

app.listen(8000, () => {
console.log('服务器启动成功');
});

六、静态服务器

koa并没有内置部署相关的功能,所以我们需要使用第三方库:

  • npm install koa-static

部署的过程类似于express:

1
2
3
4
5
6
7
8
9
10
const Koa = require('koa');
const static = require('koa-static')
const app = new Koa();

app.use(static('静态文件根目录'))

app.listen(8000, () => {
console.log('服务器启动成功');
});

七、错误处理

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

const app = new Koa();

app.use((ctx, next) => {
const isLogin = false;
if (!isLogin) {
ctx.app.emit('error', new Error('您还没有登录'), ctx);
}
});
app.on('error', (err, ctx) => {
ctx.status = 401;
ctx.body = err.message;
});

app.listen(8000, () => {
console.log('服务器启动成功');
});

ctx.body 其实是ctx.response.body做了一层代理。

但不是所以方法都做了代理。

八、Koa和express对比

从架构设计上来说:

  • express是完整和强大的,其中帮助我们内置了非常多好用的功能;
  • koa是简洁和自由的,它只包含最核心的功能,并不会对我们使用其他中间件进行任何的限制。
  • 甚至是在app中连最基本的get、post都没有给我们提供;
  • 我们需要通过自己或者路由来判断请求方式或者其他功能;

因为express和koa框架他们的核心其实都是中间件:

但是他们的中间件事实上,它们的中间件的执行机制是不同的,特别是针对某个中间件中包含异步操作时;

同步时:

  • 调用next后调用res.end(),res.end会等所有next都执行完在返回

异步时:

  • next中有异步操作,不会等异步的结果,直接执行res.end()
  • 但是koa返回的是promise对象,可以使用async await加以控制