Koa 与 Express 究竟有何不同

https://juejin.cn/post/7366526529521270796

 

三者皆是用于构建Web应用程序的Node.js框架。

  Express:

    • 设计:Express是一个基于回调函数的Web框架,它使用中间件模式来处理请求和响应。它提供了许多内置的中间件,如路由、模板引擎等,并且可以与第三方中间件集成。
    • 功能:Express具有简单、灵活和可扩展的特点。它提供了丰富的API,使得开发人员能够轻松地构建各种Web应用程序。
    • 适用场景:Express适合快速构建小型到中型的应用程序,特别是那些需要高度自定义和灵活性的项目。

  Koa:

    • 设计:Koa是一个基于Generator和async/await的Web框架,它提供了更简洁、优雅的异步编程方式。它使用了与Express类似的中间件模式,但更加注重性能和简洁性。
    • 功能:Koa具有快速、轻量级和易于扩展的特点。它提供了许多内置的中间件,并支持自定义中间件。此外,Koa还提供了上下文对象,使得开发人员能够更方便地访问请求和响应对象。
    • 适用场景:Koa适合构建大型、高性能的Web应用程序,特别是那些需要处理大量并发请求的项目。

  NestJS:

    • 设计:NestJS是一个基于TypeScript的Web框架,它采用面向对象的设计模式,如依赖注入、模块化和面向切面编程等。它提供了丰富的工具和库,使得开发人员能够更高效地构建企业级应用程序。
    • 功能:NestJS具有强大的功能,如路由、控制器、服务、依赖注入、ORM等。它还支持与多种数据库和第三方库集成,如TypeORM、Mongoose等。
    • 适用场景:NestJS适合构建大型企业级Web应用程序,特别是那些需要高度可维护性、可扩展性和可测试性的项目。

前言

当我们谈起 Koa 的时候,想必你一定会联想到 Express,同时也一定会想到技术大牛 tj

可以说 tj 对前端领域的贡献还是很大的,不仅创建了 Web 框架 Express,而且还基于 ES6 又重新打造了下一代 Web 框架 Koa,甚至创建了 Node.js 命令行工具 Commander.js。在前端领域,很多脚手架工具都是基于 Commander.js 编写的。

随着 Node.js 支持 ES6,Express 团队又基于 ES6 重新编写了下一代 Web 框架 Koa。相比于 Express,从 Koa 的发展来看,Koa 1.x 版本使用 Generator 实现异步,虽然用 Generator 实现异步相比于回调简单了很多,但是 Generator 的本意并不是异步。

相继 Koa 团队基于 ES7 开发了 Koa 2.x 的版本,也就是我们经常说的 Koa2。Koa2 完全使用 Promise 并配合 async 来实现异步。从此之后,回调地狱问题在 Koa2 中便不会存在,这也是与 Express 不同的点。

接下来我们从代码演练中来感受 Koa 与 Express 的区别,最后再做一个总结。如果你对某些概念不太理解,可访问 Koa 社区中文网进行研读,然后再跟着实操,也可以边实操边查阅。

创建项目

首先,在项目根目录终端命令行输入 npm init 初始化项目。

1.png

然后,继续在终端命令行输入 npm i koa 安装 koa 核心包。

2.png

其次,在项目根目录创建 index.js 文件,输入以下代码。

 
js
代码解读
复制代码
// 1.导入 Koa
const Koa = require('koa')

// 2.创建服务端实例对象
const app = new Koa()
// response
app.use((ctx) => {
  ctx.body = 'Hello Koa'
})

// 3.指定监听的端口
app.listen(3000)

最后,在命令行终端输入 npx nodemon index.js 运行项目,其中 nodemon 会实时监听 index.js 文件的变化,这样如果 index.js 有变动的话,我们就不用频繁的重新启动 index.js

3.png

在浏览器中输入 http://localhost:3000 就会显示上述代码中返回的文本内容 Hello Koa。

4.png

这样,一个最简易版的 Koa 项目就创建完成。

代码演练

处理静态资源

Koa 是一个轻量级的框架,它将所有的附加功能都封装到了独立的模块中,因此我们在处理静态资源的时候需要安装并引入 koa-static 这个包。

首先,在终端命令行中输入 npm i koa-static

5.png

然后,在项目根目录创建 public 文件夹,我们可以在文件夹中放入一些静态资源,比如放一张图片。

6.png

最后,在 index.js 中引入 koa-static 并注册处理静态资源的中间件。

 
js
代码解读
复制代码
// 1.导入 Koa
const Koa = require('koa')
const serve = require('koa-static') // 导入处理静态资源的模块

// 2.创建服务端实例对象
const app = new Koa()
app.use(serve('public')) // 注册处理静态资源的中间件
// response
app.use((ctx) => {
  ctx.body = 'Hello Koa'
})

// 3.指定监听的端口
app.listen(3000)

在浏览器中访问静态资源,输入 http://localhost:3000/koa.png 就能直接显示当前的图片。

7.png

处理动态资源

在 Koa 中处理动态资源,我们使用 @ladjs/koa-views 这个包。如果是老玩家可能会注意到,之前使用的是 koa-views 这个包,因为这个包已经弃用,作者推荐我们使用 @ladjs/koa-views

8.png

首先,在终端命令行中输入 npm i @ladjs/koa-views

9.png

然后,在项目根目录创建 views 文件夹,在里面新建 index.ejs 文件,输入如下内容。值得注意的是,因为我们使用的模板是 ejs,因此还需要安装 ejs,在终端命令行输入 npm i ejs

 
xml
代码解读
复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>动态网页</title>
  </head>
  <body>
    <h1>我是动态网页</h1>
    <h2><%=msg%></h2>
  </body>
</html>

最后,在 index.js 中引入 @ladjs/koa-views 并注册中间件。其中 ctx.render 的第一个参数为views 文件夹中 ejs 模板的名称,第二个参数为要传递的对象。

Koa 中的 ctx 其实就是 Express 中的 reqres 的结合体。

 
js
代码解读
复制代码
// 1.导入Koa
const Koa = require('koa')
const views = require('@ladjs/koa-views') // 导入处理动态资源的模块

// 2.创建服务端实例对象
const app = new Koa()
app.use(views('views', { extension: 'ejs' }))
// response
app.use(async (ctx) => {
  await ctx.render('index', { msg: 'Hello Koa' })
})

// 3.指定监听的端口
app.listen(3000)

在浏览器中输入 http://localhost:3000 , 就会渲染 index.ejs 模板中的内容到页面上。

10.png

处理路由

在 Koa 中处理路由,我们需要使用 koa-router 这个包。

首先,在终端命令行中输入 npm install koa-router

11.png

然后,引入 koa-router 创建路由对象并启用路由功能,在 index.js 中输入如下代码。

 
js
代码解读
复制代码
// 1.导入Koa
const Koa = require('koa')
const Router = require('koa-router') // 导入处理路由的模块
const router = new Router() // 创建路由对象

// 2.创建服务端实例对象
const app = new Koa()

// 3.处理路由
router.get('/api/blog/list', (ctx, next) => {
  ctx.body = '返回博客列表内容'
})
router.get('/api/user/login', (ctx, next) => {
  ctx.body = {
    method: 'get',
    name: '李四',
    age: 21
  }
})
router.post('/api/blog/detail', (ctx, next) => {
  ctx.body = '返回博客详情'
})
router.post('/api/user/register', (ctx, next) => {
  ctx.body = {
    method: 'post',
    name: '王五',
    age: 22
  }
})

app
  .use(router.routes()) // 启动路由功能
  .use(router.allowedMethods()) // 自动设置响应头

// 3.指定监听的端口
app.listen(3000)

最后,在浏览器中访问 http://localhost:3000/api/user/login ,便会显示上述代码中的返回的信息。

12.png

处理请求参数

在 Koa 中处理请求参数直接使用 ctx.request 来获取参数,我们基于上述处理路由部分的代码进行演示。

get 请求参数的处理

在 Koa 中获取 get 请求的参数,如果是以查询字符串的形式,需要从 ctx.request.query中获取;如果是以 params 的形式,则需要从 ctx.request.params 中获取,在 index.js 中输入如下代码。

 
js
代码解读
复制代码
// 1.导入 Koa
const Koa = require('koa')
const Router = require('koa-router') // 导入处理路由的模块
const router = new Router() // 创建路由对象

// 2.创建服务端实例对象
const app = new Koa()

router.get('/user', (ctx, next) => {
  let request = ctx.request
  console.log(request.query) // 获取转换成对象之后的 get 请求参数
  console.log(request.querystring) // 获取字符串形式的 get 请求参数
})
router.get('/login/:name/:age', (ctx, next) => {
  console.log(ctx.params)
})

// 处理路由
app
  .use(router.routes()) // 启动路由功能
  .use(router.allowedMethods()) // 自动设置响应头

// 3.指定监听的端口
app.listen(3000)

以查询字符串的形式获取 get 请求参数,在浏览器中输入 http://localhost:3000/user?name=lisi&age=21 ,在控制台中就会打印如下信息。

13.png

params 的形式获取 get 请求参数,在浏览器中输入 http://localhost:3000/login/wuwang/22 ,在控制台中就会打印如下信息。

14.png

post 请求参数的处理

在终端终端命令行输入 npm install koa-bodyparser

在 Koa 中获取 post 请求的参数,需要从 ctx.request.body中获取,因此这时候需要导入处理 post 请求参数的模块 koa-bodyparser,并注册处理 post 请求参数的中间件,在 index.js 中输入如下代码。

 
js
代码解读
复制代码
// 1.导入 Koa
const Koa = require('koa')
const Router = require('koa-router') // 导入处理路由的模块
const bodyParser = require('koa-bodyparser') // 导入处理post请求参数的模块
const router = new Router() // 创建路由对象

// 2.创建服务端实例对象
const app = new Koa()

app.use(bodyParser()) // 注册处理post请求参数的中间件

router.post('/user', (ctx, next) => {
  let request = ctx.request
  console.log(request.body) // 获取 post 请求参数
})

// 处理路由
app
  .use(router.routes()) // 启动路由功能
  .use(router.allowedMethods()) // 自动设置响应头

// 3.指定监听的端口
app.listen(3000)

在 postman 工具中测试传入参数 nameage,点击 Send。

1.png

在控制台就能够接收到对应的值。

3.png

处理 Cookie

在 Koa 中处理 Cookie 直接使用 ctx.cookies.set 来设置 Cookie,使用 ctx.cookies.get 来获取 Cookie。

 
js
代码解读
复制代码
// 1.导入 Koa
const Koa = require('koa')
const Router = require('koa-router') // 导入处理路由的模块
const router = new Router() // 创建路由对象

// 2.创建服务端实例对象
const app = new Koa()

// 设置 Cookie
router.get('/setCookie', (ctx, next) => {
  ctx.cookies.set('name', 'lisi', {
    path: '/', // 指示 cookie 路径的字符串(默认为 `/`)
    httpOnly: true, // 一个布尔值,指示 cookie 是否仅通过 HTTP(S) 发送,而不可供客户端 JavaScript 使用(默认为 `true`)
    maxAge: 24 * 60 * 60 * 1000 // 一个数字,表示从 `Date.now()` 到到期的毫秒数。
  })
})

// 获取 Cookie
router.get('/getCookie', (ctx, next) => {
  console.log(ctx.cookies.get('name'))
})

// 处理路由
app
  .use(router.routes()) // 启动路由功能
  .use(router.allowedMethods()) // 自动设置响应头

// 3.指定监听的端口
app.listen(3000)

处理错误

在 Koa 中处理错误需要使用 koa-onerror 这个包,在终端命令行输入 npm install koa-onerror

 
js
代码解读
复制代码
// 1.导入 Koa
const Koa = require('koa')
const Router = require('koa-router') // 导入处理路由的模块
const onerror = require('koa-onerror') // 导入处理错误的模块
const router = new Router() // 创建路由对象

// 2.创建服务端实例对象
const app = new Koa()
onerror(app) // 告诉 koa-onerror 我们需要捕获所有服务端实例对象的错误

router.get('/user', (ctx, next) => {
  let request = ctx.request
  console.log(request.query)
})

// 处理路由
app
  .use(router.routes()) // 启动路由功能
  .use(router.allowedMethods()) // 自动设置响应头

// 处理错误
app.use((err, ctx) => {
  console.log(err.status, err.message)
  ctx.body = err.message
})

// 3.指定监听的端口
app.listen(3000)

例如,在浏览器中输入 http://localhost:3000/123 ,没有匹配到对应的路由,浏览器页面就会抛出错误。

4.png

如果打印在在控制台中也会显示对应的错误信息。

5.png

中间件的使用

中间件可以说是 Koa 和 Express 的核心,它们中间件的执行机制是不同的,尤其包含异步操作的时候。通过对比它们不同的使用方式我们就能够发现其中的区别。

koa 中间件

使用 koa 执行同步操作

依次编写三个中间件,在第一个中间件中返回结果 ctx.body = ctx.msg,为什么在第一个中间件中能够返回结果,这是由于 Koa 的洋葱模型

 
js
代码解读
复制代码
const Koa = require('koa')

// 创建app对象
const app = new Koa()

// 注册中间件
app.use((ctx, next) => {
  console.log('koa middleware01')
  ctx.msg = 'aaa'
  next()

  // 返回结果
  ctx.body = ctx.msg
})

app.use((ctx, next) => {
  console.log('koa middleware02')
  ctx.msg += 'bbb'
  next()
})

app.use((ctx, next) => {
  console.log('koa middleware03')
  ctx.msg += 'ccc'
})

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

上述代码的执行逻辑是,先由外到内执行第一个中间件,遇到 next() 接着执行第二个中间件,在第二个中间件中遇到 next() 接着执行第三个中间件,这样就依次执行完 next() 函数前面的内容。

因为第三个中间件没有 next(),此时会从第三个中间件开始由内到外执行 next() 后面的内容,接着执行第二个中间件 next() 后面的内容,再执行第一个中间件 next() 后面的内容,也就是 ctx.body = ctx.msg 这行代码,最终会在页面显示正确的文本内容 aaabbbccc。

13.png

这种先由外到内执行请求的逻辑,也可以理解为 next() 函数之前的内容,然后再由内到外执行响应的逻辑,也可以理解为 next() 函数之后的内容,就是 Koa 的洋葱模型

其实每个中间件都有两次处理:一次在next()之前,另一次在next()之后。

21.png

使用 Koa 执行异步操作

同样依次编写三个中间件,在第一个中间件中返回结果 ctx.body = ctx.msg

如果执行的下一个中间件是一个异步函数,那么 next() 函数默认不会等到中间件的结果,就会执行下一步操作,如果我们希望等待下一个异步函数的执行结果,那么需要在 next() 函数前面加 await,从而实现洋葱模型,这是由于 Koa 的中间件函数使用 async 关键字定义时,它会自动返回一个 Promise 函数。

 
js
代码解读
复制代码
const Koa = require('koa')
const axios = require('axios')

// 创建app对象
const app = new Koa()

// 注册中间件
// 1.koa的中间件1
app.use(async (ctx, next) => {
  console.log('koa middleware01')
  ctx.msg = 'aaa'
  await next()

  // 返回结果
  ctx.body = ctx.msg
})

// 2.koa的中间件2
app.use(async (ctx, next) => {
  console.log('koa middleware02')
  ctx.msg += 'bbb'
  // 在 next() 函数前面加 await 等待下一个异步函数的执行结果
  await next()
  console.log('----')
})

// 3.koa的中间件3
app.use(async (ctx, next) => {
  console.log('koa middleware03')
  // 网络请求
  const resData = await axios.get('https://m.maoyan.com/ajax/movieOnInfoList')
  ctx.msg += resData.data.movieList[0].nm
})

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

express 中间件

使用 express 执行同步操作

同样依次编写三个中间件,在同步代码中,这部分执行逻辑和 Koa 中间件的执行逻辑是相同的。也就是说在同步代码中 Koa 与 Express 没有区别,在同步代码中,Express 也是具有洋葱模型的。

 
js
代码解读
复制代码
const express = require('express')

// 创建app对象
const app = express()

// 编写中间件
app.use((req, res, next) => {
  console.log('express middleware01')
  req.msg = 'aaa'
  next()
  // 返回值结果
  res.json(req.msg)
})

app.use((req, res, next) => {
  console.log('express middleware02')
  req.msg += 'bbb'
  next()
})

app.use((req, res, next) => {
  console.log('express middleware03')
  req.msg += 'ccc'
})

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

使用 express 执行异步操作

同样依次编写三个中间件,这部分执行逻辑与 Koa 中是不同的,在异步代码中 Express 是没有洋葱模型的,也就是说无论是在异步代码中还是同步代码中,Koa 都是具有洋葱模型的,都是生效的

因此,Express 在异步代码中 res.json(req.msg) 只能在最后一个中间件中返回,而不能在第一个中间件中返回,因为如果执行的下一个中间件是一个异步函数,那么 next() 函数默认不会等到中间件的结果,并不能像 Koa 那样使用 asyncawait 那样来等待异步结果的返回。

 
js
代码解读
复制代码
const express = require('express')
const axios = require('axios')

// 创建app对象
const app = express()

// 编写中间件
app.use(async (req, res, next) => {
  console.log('express middleware01')
  req.msg = 'aaa'
  await next()
  // 在这里是没有返回值的
  // res.json(req.msg)
})

app.use(async (req, res, next) => {
  console.log('express middleware02')
  req.msg += 'bbb'
  await next()
})

// 执行异步代码
app.use(async (req, res, next) => {
  console.log('express middleware03')
  const resData = await axios.get('https://m.maoyan.com/ajax/movieOnInfoList')
  req.msg += resData.data.movieList[0].nm
  console.log(resData.data.movieList[0].nm)
  // 只能在这里返回结果
  res.json(req.msg)
})

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

总结

通过上述代码的演练,我们可以总结一个结论,Express 与 Koa 最大的区别就是实现异步的方式不同

Express 使用回调函数来实现异步,这会带来一个回调地狱的问题,但是它的语法比较经典,兼容性比较好。而 Koa 1.x 的版本使用生成器 Generator 来实现异步,虽然解决了回调地狱的问题,但是生成器的本质并不是异步。随后 Koa2 使用 Promise 并配合 async 来实现异步, 虽然解决了回调地域的问题, 但是语法太新导致兼容性不太好。

还有一点不同的是,Express 内置了很多封装好的功能,我们直接从中引入就能使用。而 Koa 主打的就是一个轻量化,很多功能都是独立的模块,我们只需要按需下载对应的包即可,这些包官网都会为我们提供。


作者:进击的松鼠
链接:https://juejin.cn/post/7366526529521270796
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2024-09-22 21:05  China Soft  阅读(13)  评论(0编辑  收藏  举报