# Koa2 框架,中间件、洋葱圈模型、路由、错误处理实践

TIP

从本节内容开始,正式开始学习 NodeJS 框架之一 Koa2,从基础的环境搭建、中间件 与 洋葱圈模型及相关实践。

前面我们学习了 NodeJS 的基础,但在实际项目开发中是不会从 0 开始手写的(如:项目中处理网络请求不会从 0 开始手写怎么获取 request、Querystring、前端传回来的数据、返回给前端的 JSON 格式等)这些都不会从 0 开始手写。包括其他使用语言的项目也一样都不会从 0 开始手写。

软件工程的发展可以追溯到上世纪六十年代末至七十年代初,至今已经经历了五十多年,很多地方已经做到非常细致的分工,有很多东西可以供我们直接拿来就用

  • 会借助一些工具,如:npm i xxx 相关的包
  • 会选择一个框架,如:NodeJS(Koa2、NestJS),Java(Spring、Spring Boot、MyBatis、Dubbo),前端框架(Vue、React)
  • 没有工具 和 框架的编程语言,将不可用于实际企业项目

本次关于 koa2 框架所学习的相关知识:

  • koa2 是什么
  • koa2 环境搭建
  • koa2 处理 HTTP
  • koa2 中间件
  • koa2 洋葱圈模型
  • 路由
  • 请求参数解析
  • 错误处理

# 一、Koa2 简介 和 初体验

TIP

  • 什么是框架
  • koa2 是 nodejs web server 框架
  • koa2 的安装 和 基本使用

# 1、koa2 是什么

TIP

深入浅出什么是框架,koa2 框架、koa2 的安装 和 基本使用等

# 1.1、什么是框架

TIP

框架英文翻译 frame,类比 Vue

  • 封装原生代码的 API
  • 规范流程 和 格式
  • 让开发人员更加关注于业务代码,提高开发效率(即:不需要关注原生的 API、流程规范等,框架已经帮我们完成了)

因此,我们在日常项目开发中就会使用框架来规范我们的开发范式 和 流程。我们之前通过 Vue 项目应该已经有所感受了。

# 1.2、框架 frame 和 库 lib 的区别

TIP

  • 框架是唯一的,库可以共存。即:项目开发中框架只能选择一个,不可能同时使用多个框架。库是可以使用多个的
  • 框架关注全流程,库关注单个功能
  • 类比 Vue 和 axios

# 1.3、koa2 是 NodeJS web server 框架

TIP

Koa - 基于 Node.js 平台的下一代 web 开发框架

  • 官网 和 文档 https://koa.bootcss.com/ (opens new window)
  • 通过 async/await 语法高效编写 web server(即:async/await 是 JS 语法中解决异步编程最有效的书写方式)
  • 中间件机制,能合理拆分业务代码

# 2、koa2 的安装 和 初体验

TIP

创建项目文件夹 icoding-koa ,使用 VSCode 打开,在命令行终端中输入如下指令

初始化 package.json

npm init -y

安装 koa2

# 生产依赖
npm i koa --save

/src/index.js 中体验 Hello world

// 1、导入 koa
const Koa = require("koa");
// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件
// ctx(context)上下文
app.use((ctx) => {
  ctx.body = "hello koa2";
});

// 4、启动服务,监听 3000 端口
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 命令行终端启动服务

node .\src\index.js

image-20240126194532197

# 3、使用 nodemon

安装 nodemon,使用 nodemon 启动 node 服务。会帮助我们实时监听 JS 文件的改变而自动重启服务

# -D 开发依赖
npm i nodemon -D

使用 nodemon 启动

# 直接使用 nodemon 有时会提示找不到
nodemon .\src\index.js

# 以上找不到,可使用 npx nodemon 的方式,即:会在当前的 node_modules 中去查找 nodemon,如果有会直接使用,如没有就会使用全局的,如还没有就会去安装 nodemon
npx nodemon .\src\index.js

或 在 package.json 中,定义启动运行脚本

{
  "scripts": {
    // 定义 nodemon 的启动运行脚本
    "dev": "nodemon ./src/index.js"
  }
}

在浏览器地址栏中输入 http://localhost:3000/

image-20240204180444932

在 postman 中测试

image-20240204180954727

# 二、中间件

TIP

深入浅出什么是中间件,基本使用、链式调用 等

# 1、什么是中间件

TIP

中间件:字面意思是 “在什么的中间”,即:在 HTTP 请求和响应中间的处理程序

image-20240219133548318

注:

对于处理请求来说,在响应发出之前,可以在请求和响应之间做一些操作,并且可以将这个处理结果传递给下一个函数继续处理(整个流程见上图)

每一个 HTTP 就会对应一个响应,我们知道 HTTP 协议是一个一问一答的形式

  • 前端每次向服务端发送的 HTTP 请求都会交由一个处理函数进行处理,当处理函数业务逻辑特别复杂时,一个函数就会写很长
  • 此时,就会拆分成若干个函数,第一个函数处理完成后、交由第二个函数处理、再交由第三个函数处理 ...... 最后整个中间件处理完成后就会将结果进行返回
  • 上图中 HTTP 请求和响应中间执行的函数就叫做 中间件
  • 本质上服务端处理 HTTP 请求就类似于一个流水线作业,如果一个响应的处理函数特别复杂时,可以拆分成若干个函数,其中的每一个函数就叫做 中间件

因此,我们讲 有时候从 HTTP 请求到响应的业务比较复杂,将这些复杂的业务拆开成一个个功能独立的函数,就是中间件

# 2、中间件的基本使用

/src/middleware.js 中,体验自定义中间件的使用

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件(在 use 中写一个函数,即:中间件)
app.use((ctx, next) => {
  console.log("我来组成头部 ~~");
  // 调用 next(),即:交由下一个中间件来处理
  next();
});

// 下一个中间件
app.use((ctx, next) => {
  console.log("我来组成身体 ~~");
  next();
});

// 下一个中间件,后边就没有再下一个中间件了,因此不再需要 next 参数
app.use((ctx) => {
  console.log("组装完成 !");
});

// 4、启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 命令行终端中输入命令,启动服务

npx nodemon .\src\middleware.js

在浏览器地址栏中输入 http://localhost:3000/

image-20240205181922222

注:

在 VSCode 控制台会依次打印输出 “头部、身体、组装完成”

  • 会依次执行每一个中间件,先执行第一个中间件,打印输出 “我来组成头部 ~~”,调用 next()
  • 执行第二个中间件,打印输出 “我来组成身体 ~~”,调用 next()
  • 执行第三个中间件,打印输出 “组装完成”,没有再调用 next()
  • 同时,整个过程中都没有给客户端(浏览器)返回任何内容,因此会返回 Not Found

image-20240205182520792

在 postman 中测试

image-20240205182239756

注:

以上返回 Not Found ,如我们希望返回正确的内容,这需要在中间件中使用 ctx.body 来进行返回

修改 /src/middleware.js ,在 最后一个中间件中添加 ctx.body

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件(在 use 中写一个函数,即:中间件)
app.use((ctx, next) => {
  console.log("我来组成头部 ~~");
  // 调用 next(),即:交由下一个中间件来处理
  next();
});

// 下一个中间件
app.use((ctx, next) => {
  console.log("我来组成身体 ~~");
  next();
});

// 下一个中间件,后边就没有再下一个中间件了,因此不再需要 next 参数
app.use((ctx) => {
  console.log("组装完成 !");

  // ------- 返回 客户端--------
  ctx.body = "组装完成";
});

// 4、启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

修改完成后,保存代码后 nodemon 会自动重启服务

image-20240205230233984

在 postman 中测试

image-20240205230339494

# 3、链式调用

TIP

app.use() 实际上会返回 this,即:返回的是 app 对象本身。意味着我们可以在 use() 后边再写一个 use() 就是链式调用的形式

官方文档对于 app.use() 链式调用 (opens new window) 的写法,可查阅文档

image-20240205234135964

改造 /src/middleware.js 中的 app.use() 代码部分(去掉 app 直接使用链式调用)

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件(在 use 中写一个函数,即:中间件)
app
  .use((ctx, next) => {
    console.log("我来组成头部 ~~");
    // 调用 next(),即:交由下一个中间件来处理
    next();
  })
  .use((ctx, next) => {
    console.log("我来组成身体 ~~");
    next();
  })
  .use((ctx) => {
    console.log("组装完成 !");
    // 返回 客户端
    ctx.body = "组装完成";
  });

// 4、启动服务(此处的 app 也可以省略掉)
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

注:

app.use() 中只能接受一个函数作为参数,即:一个 use 只能注册一个中间件。

而 Express 框架中可以接受多个函数作为参数,即:一个 use 可同时注册多个中间件

# 三、洋葱圈模型

TIP

洋葱圈模型(也称为洋葱模型或洋葱架构)在软件开发中,特别是在 Node.js 和其他后端技术中,经常被用来描述软件系统的层次结构。洋葱模型强调核心业务逻辑和外部依赖之间的分离,并鼓励将代码组织成可维护的、松耦合的同心层。

以下是洋葱圈模型的基本解释和它在 Node.js 中的应用:

  • 核心与外围:洋葱模型的中心是核心业务逻辑,这是软件的最重要部分,通常不依赖于外部系统或库。外围层则包含与外部系统(如数据库、API、文件系统等)的交互,这些层离核心越远,依赖性越高。

  • 依赖的方向:每一层只能依赖其内部的层,不能依赖其外部的层。这意味着如果你有一个三层结构(内层、中层和外层),中层可以依赖内层,但不能依赖外层。这确保了代码的高内聚和低耦合。

  • 测试:由于依赖的方向性,内层的代码可以更容易地进行单元测试,因为它们不依赖于外部系统。外层代码则可能需要进行集成测试,因为它们与外部系统有交互。

  • 在 Node.js 中的应用:在 Node.js 项目中,洋葱模型可以通过模块和依赖注入来实现。例如,你可以将核心业务逻辑放在一个模块中,然后将这个模块注入到依赖外部系统的模块中。这样,核心业务逻辑就不直接与外部系统交互,而是通过注入的模块进行交互。

  • 优点

  • 可维护性:由于代码是分层和松耦合的,所以更容易维护和修改。

  • 可测试性:由于依赖的方向性,可以更容易地进行单元测试。

  • 灵活性:由于核心业务逻辑与外部系统的分离,可以更容易地替换或添加外部系统。

  • 挑战:实施洋葱模型可能需要一些重构工作,特别是对于那些已经有大量代码的现有项目。此外,需要确保开发团队理解并遵循这种架构风格。

总之,洋葱圈模型是一个强大的架构工具,可以帮助你构建可维护、可扩展和可测试的 Node.js 应用程序。

image-20240206103508620

# 1、洋葱圈模型的执行过程

/src/onion-circle-model.js

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件
app
  .use((ctx, next) => {
    // 观察执行顺序
    console.log(1);
    next();
    console.log(2);
  })
  .use((ctx, next) => {
    console.log(3);
    next();
    console.log(4);
  })
  .use((ctx) => {
    console.log(5);
    ctx.body = "处理完成";
  });

// 4、启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 命令行终端中输入命令,启动服务

npx nodemon .\src\onion-circle-model.js

在 postman 或 浏览器中发送请求

image-20240206131315585

在 VSCode 命令行终端中,查看打印输出信息

image-20240206131518071

注:

可以看到以上代码的执行顺序并非 1 2 3 4 5 ,它的执行顺序是 1 3 5 4 2,解析如下

  • 执行第一个中间件,先打印输出 1
  • 然后调用 next() ,直接进入第二个中间件,打印输出 3
  • 再调用 next() ,进入第三个中间件, 打印输出 5,同时返回客户端 “处理完成”
  • 此时,第二个中间件中的 next() 执行结束,打印输出 4
  • 第一个中间件中的 next() 执行结束,打印输出 2

即:整个过程是一层一层地往下找,类似上边的洋葱圈,先一层一层地往里走,再一层一层地返回回来,这就是洋葱圈模型的执行过程

# 2、异步处理

TIP

如果中间件中存在一些异步的代码,Koa 也提供了统一的处理方式

可查看 koa 框架的源代码,在核心源码的 /node_modules/koa/lib/application.js 文件中

  • 在源码中 向外暴露的 Application 类(导出了),就是 Koa,我们通过它可以实例化一个对象,它继承了 Emitter(发布订阅模式)

  • 存放中间件的是一个数组 this.middleware = [],每个中间件是一个函数

  • 具体执行,在 listen(...args) 中,会调用 NodeJS 自带的 http 模块 http.createServer(this.callback()),来创建一个 server,会将 this.callback() 的返回值作为参数传入 createServer()

  • 进入 callback() 方法中,在 callback 方法中,会调用 koa-compose 中的 compose() 方法,将中间件数组 this.middleware 解析成具体的函数,compose() 返回一个 Promise

  • 由于 compose() 是通过 koa-compose 导入的,因此在 node_moduleskoa-compose 里的 index.js 中找到 compose() 方法。如果参数不是一个数组,会抛出 TypeError;参数数组里的每个元素都要是 function 类型,否则也会抛出 TypeError

  • 返回 return function(context, next) {},function 函数最终返回 return dispatch(0) ,dispatch 是 function dispatch(i) {} 最终返回的是一个 Promise,dispatch 其实就是我们执行中间件中的 next(),其中 dispatch.bind(null, i + 1))i + 1 即:执行下一个中间件,因此本质上 next() 方法返回的是一个 Promise(接下来就会做异步处理)

# 2.1、async await 语法

TIP

如果中间件中存在一些异步的代码,Koa 也提供了统一的处理方式

  • async:声明异步函数
  • await:等待,等待一个 Promise 的结果,后跟一个 Promise 对象

如果要使用 await,需要在函数声明前加上async

# 2.2、async 语法的基本使用

src/async-await.js

// async await 是关键字
// async 用来修饰函数,将函数的返回值封装成 Promise 对象

// 定义一个 foo 函数
function foo() {
  return 123;
}

// 调用 foo 函数
const res = foo();
console.log(res); // 123

在 VSCode 命令行终端中输入如下命令,运行

npx nodemon .\src\async-await.js

控制台中输出结果 123

image-20240215130223289

给 foo 函数加上 async 关键字后,会返回什么结果

// async await 是关键字
// async 用来修饰函数,将函数的返回值封装成 Promise 对象

// 定义一个 foo 函数
// 添加 async 关键字
async function foo() {
  return 123;
}

// 调用 foo 函数
const res = foo();
console.log(res); // Promise { 123 } 为一个 Promise 对象

image-20240215131004774

返回的 Promise 对象中就会存在 状态 和 结果(状态是 9999,结果是 123),因此就可以通过 .then() 方法来得到 Promise 对象中的结果

// async await 是关键字
// async 用来修饰函数,将函数的返回值封装成 Promise 对象

// 定义一个 foo 函数
// 添加 async 关键字
async function foo() {
  return 123;
}

// 调用 foo 函数
const res = foo();
console.log(res); // Promise { 123 }

// 通过 .then() 方法来得到 Promise 对象中的结果
res.then((data) => {
  console.log(data); // 123
});

image-20240215131340007

# 2.3、await 语法的基本使用

src/await.js

// await 不能单独使用,必须跟 async 连用
await 123;

报错:await 仅在异步函数和模块的顶层主体中有效

image-20240215133545433

正确的用法

// await 不能单独使用,必须跟 async 连用

// await 后边必须跟一个 Promise 的对象,表达式返回 Promise 的结果
async function foo() {
  const res = await 123;
  console.log(res); // 得到的 123,是一个 Promise 的结果
}

// 调用 foo
foo(); // 123

以上 async、await 的书写方式就等同于 如下写法

// await 不能单独使用,必须跟 async 连用

// await 后边必须跟一个 Promise 的对象,表达式返回 Promise 的结果
async function foo() {
  // const res = await 123
  // console.log(res) // 得到的 123,是一个 Promise 的结果

  // 以上就等同于 如下写法

  // await 关键字会先把 123 转换成一个 Promise 对象
  // Promise 对象会调用一个 resolve 方法,将当前 Promise 的结果设置为 123,状态为 fullfiled

  const p = new Promise((resolve, reject) => {
    // 调用 resolve,p 的结果是 123,状态为 fullfiled
    resolve(123);
  });

  // 此时,res 就可以直接拿到 Promise 对象的结果,在没用 await 关键字之前用 .then。
  // 因此,我们可以认为 await 就是 .then 的一个语法糖,可通过这种方式快速的拿到 Promise 的结果
  const res = await p;
  console.log(res); // 得到的 123,是一个 Promise 的结果
}

// 调用 foo
foo(); // 123

总结:

  • async 表示该函数是一个异步函数。async 用来修饰函数,将函数的返回值封装成 Promise 对象
  • async 关键字是一个语法糖,即:我们自己写 Promise 会非常麻烦,使用 async 关键字,底层帮我们把以上的 123 翻译成了一个 Promise 对象,这样我们用来就非常的简洁高效(甜),因此叫语法糖。
  • await 后边必须跟一个 Promise 的对象,如果不是 Promise 对象,它会将其转换为 Promise 对象。整个 await p 表达式返回的是该 Promise 对象的结果(类似通过 .then 拿结果),从语法结构上看更像同步代码。它会阻止代码往下执行,一直等到 Promise 的结果返回后在接着执行下面的代码

# 3、koa 处理同步数据

TIP

实现如下需求

  • 在 中间件 ① 中,构造一个 message = aa
  • 在 中间件 ② 中,同步追加 bb
  • 在 中间件 ③ 中,同步追加 cc
  • 最终在 中间件 ① 中,通过 body 返回数据

/src/koa-sync.js

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件
app.use((ctx, next) => {
  // 在 中间件 ① 中,构造一个 `message = aa`
  ctx.message = "aa";
  next();
  // 在 中间件 ① 中,通过 body 返回数据
  ctx.body = ctx.message;
});

app.use((ctx, next) => {
  // 在 中间件 ② 中,同步追加 `bb`
  ctx.message += "bb";
  next();
});

app.use((ctx) => {
  // 在 中间件 ③ 中,同步追加 `cc`
  ctx.message += "cc";
});

// 4、启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-sync.js

image-20240216140510192

注:以上为 koa 中处理同步数据的写法

# 4、koa 处理异步数据

TIP

实现如下需求

  • 在 中间件 ① 中,构造一个 message = aa
  • 在 中间件 ② 中,同步追加 bb
  • 在 中间件 ③ 中,异步追加 cc
  • 最终在 中间件 ① 中,通过 body 返回数据

src/koa-async.js

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件
app.use((ctx, next) => {
  // 在 中间件 ① 中,构造一个 `message = aa`
  ctx.message = "aa";
  next();
  // 在 中间件 ① 中,通过 body 返回数据
  ctx.body = ctx.message;
});

app.use((ctx, next) => {
  // 在 中间件 ② 中,同步追加 `bb`
  ctx.message += "bb";
  next();
});

// 在同步代码中,使用异步处理的代码
app.use((ctx) => {
  // 返回一个 Promise 对象,状态是 fulfilled,结果是 cc
  Promise.resolve("cc").then((data) => {
    // .then 中的代码 为 异步代码
    // 在 中间件 ③ 中,异步追加 `cc`
    ctx.message += "cc";
  });
});

// 4、启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-async.js

image-20240216142430504

注:

以上代码执行后,最终输出 aabb,并没有拼接 cc

原因是:.then 中的代码为异步执行,而外边的部分为同步代码,外边的同步已经执行完了,因此就直接返回了 aabb,并没有拼接 cc

# 5、koa 中使用 async、await

TIP

使用 async 、await 关键字重构以上需求

src/koa-async-await.js 中使用 async 和 await 关键字

// 1、导入 koa 包
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件
app.use(async (ctx, next) => {
  ctx.message = "aa";
  await next();
  ctx.body = ctx.message;
});

app.use(async (ctx, next) => {
  ctx.message += "bb";
  await next();
});

app.use(async (ctx) => {
  // 返回一个 Promise 对象,状态是 fulfilled,结果是 cc
  const res = await Promise.resolve("cc");
  ctx.message += res;
});

// 4、启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-async-await.js

image-20240216140510192

注:

await 会等待 next() 执行返回结果后,才会执行下面的代码。

因此,在 koa 中是可以实现在中间件里处理异步请求的。而在 express 框架实现起来就会非常麻烦

# 四、路由

TIP

在 web 应用框架中一个非常重要的概念 路由。即:对于一个 HTTP 服务来说需要根据不同的 URL 去返回不同的 内容。

  • 路由的基本概念 和 应用
  • 中间件 koa-router

# 1、什么是路由

TIP

  • 建立 URL 和 处理函数之间的对应关系
  • 主要作用:根据不同的 Method(请求方法) 和 URL 返回不同的内容

# 1.1、路由的应用

TIP

实现需求:根据不同的 Method 和 URL,返回不同的内容(即:用 koa 原生的方式来实现)

  • GET 请求 /,返回 “我是主页”
  • GET 请求 /user ,返回 “我是用户页”
  • POST 请求 /user ,返回 “我是创建用户页”

src/koa-route.js

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件(所谓中间件实际就是一个函数)
app.use((ctx) => {
  // 查看 ctx 到底是什么 ?
  console.log(ctx); // http 上下文(http请求 + http响应)

  // 主要常用的两个对象
  // ①、ctx.request // http 请求
  // ②、ctx.response // http 响应
});

// 4、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-route.js

image-20240217095513290

查看 VSCode 终端打印信息

image-20240217095729012

注:

以上打印的 ctx 对象信息,其中

  • request 为 HTTP 请求信息,其中 method 为请求方式、url 地址、header 为请求头
  • response 为 HTTP 响应信息,其中 status 为响应状态码,message 响应信息,header 响应的响应头

# 1.2、路由请求判断

src/koa-route.js 中,判断路由地址

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件(所谓中间件实际就是一个函数)
app.use((ctx) => {
  // 查看 ctx 到底是什么 ?
  console.log(ctx); // http 上下文(http请求 + http响应)

  // 主要常用的两个对象
  // ①、ctx.request // http 请求
  // ②、ctx.response // http 响应

  // 判断请求 url 不同,返回不同的内容
  if (ctx.request.url == "/") {
    ctx.response.body = "我是主页";
  } else if (ctx.request.url == "/user") {
    if (ctx.request.method == "GET") {
      ctx.response.body = "我是用户页";
    } else if (ctx.request.method == "POST") {
      ctx.response.body = "我是创建用户页";
    } else {
      ctx.response.status = 405; // 请求方式不支持
    }
  } else {
    ctx.response.status = 404; // 请求 url 不存在
  }
});

// 4、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

GIF-2024-2-17-10-12-53

# 1.3、简化请求和响应的写法

src/koa-route.js 中,简化请求和响应的写法

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、编写中间件(所谓中间件实际就是一个函数)
app.use((ctx) => {
  // 查看 ctx 到底是什么 ?
  console.log(ctx); // http 上下文(http请求 + http响应)

  // 主要常用的两个对象
  // ①、ctx.request // http 请求
  // ②、ctx.response // http 响应

  if (ctx.url == "/") {
    ctx.body = "我是主页";
  } else if (ctx.url == "/user") {
    if (ctx.method == "GET") {
      ctx.body = "我是用户页";
    } else if (ctx.method == "POST") {
      ctx.body = "我是创建用户页";
    } else {
      ctx.status = 405; // 请求方式不支持
    }
  } else {
    ctx.status = 404; // 请求 url 不存在
  }
});

// 4、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

注:

以上代码中,在判断请求和响应的部分去掉 request 和 response,直接使用 ctx.urlctx.methodctx.bodyctx.status 在 koa 中也是支持的

实际上,去掉 request 和 response 也能支持是因为在 koa 中做了一次代理,即:当我们在访问 ctx.url 时,实际访问的是 ctx.request.url;当访问 ctx.body 时,实际访问的是 ctx.response.body

具体在 koa 源代码中可以看到 ,感兴趣可查阅。在实际开发中不会直接写到同一个函数中来判断,会使用社区提供的一个中间件来处理路由相关的内容。以上为路由实现的基本原理与核心思想(koa 原生的方式来实现路由跳转)。

# 2、koa-router

TIP

以上我们通过 koa 原生的方式来实现路由跳转,可读性很差,在业务比较复杂后该写法就非常的不友好 且 不优雅。

在社区中提供了 koa-router (opens new window) 中间件来实现路由挑战

安装 koa-router

npm i koa-router

src/koa-router.js

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、使用 koa-router

// 3.1、导入 koa-router
const Router = require("koa-router");
// 3.2、实例化 router 路由对象
const router = new Router();
// 3.3、编写路由规则
router.get("/", (ctx) => {
  ctx.body = "我是主页";
});
router.get("/user", (ctx) => {
  ctx.body = "我是用户页";
});
router.post("/user", (ctx) => {
  ctx.body = "我是创建用户页";
});

// 4、注册路由中间件
app.use(router.routes());
// 支持报 405、501 错误
app.use(router.allowedMethods());

// 5、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-router.js

GIF-2024-2-17-12-34-32

注:

注册了 router.allowedMethods() 中间件后,出错后会报 405、501 错误,501 在不常见的请求方式时会报,如 LINK 请求

以上 整个 3 使用 koa-router 部分也可单独抽离出来

# 3、分离 router 路由层

TIP

在实际开发中将一个模块放到一个单独的文件中,抽离一个 router 路由层。分层后模块化更加清晰,文件的拆分结构可读性更强,更有利于后期的维护。

创建 src/router/user.route.js 文件,编写与 user 路由相关的代码

  • 即:在 src 文件夹下创建 router 文件夹,多一个 r 表示一个路由对象。
  • 新建文件名为 user.route.js ,不添加 r 表示一个路由规则;表示 user 模块的 路由规则

将以上代码中 第 3 个部分单独抽离出来

// 3、使用 koa-router

// 3.1、导入 koa-router
const Router = require("koa-router");
// 3.2、实例化 router 路由对象
const router = new Router();

// 3.3、编写路由规则
router.get("/user", (ctx) => {
  ctx.body = "我是用户页";
});
router.post("/user", (ctx) => {
  ctx.body = "我是创建用户页";
});

module.exports = router;

再导入至 src/koa-router.js

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 3、导入 router 路由
const userRoute = require("./router/user.route");

// 4、注册路由中间件
// 将 原来的 router 替换成 userRoute ,同时变成链式调用
app.use(userRoute.routes()).use(userRoute.allowedMethods());

// 5、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

此时,可同样实现 GET、POST 请求 /user 路由规则的跳转

还可对 src/router/user.route.js 路由再次优化,使得代码更加简洁。即:给路由设置一个统一的前缀

// 3、使用 koa-router

// 3.1、导入 koa-router
const Router = require("koa-router");
// 3.2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 3.3、编写路由规则
router.get("/", (ctx) => {
  ctx.body = "我是用户页";
});
router.post("/", (ctx) => {
  ctx.body = "我是创建用户页";
});

module.exports = router;

# 五、请求参数解析

TIP

深入浅出请求参数的解析,相关应用场景、处理 URL 参数、处理 body 参数 等

# 1、应用场景

TIP

在很多实际应用场景中,后端都需要解析请求的参数,作为数据库操作的条件

# 1.1、场景一

TIP

前端通过请求,获得 id = 1001 的用户信息

image-20240217185817874

接口设计

GET  /user/:id

# 1.2、场景二

TIP

前端通过查询年龄在 18 到 25 岁之间的用户信息

image-20240217190620209

接口设计

GET  /user?start=18&end=25

# 1.3、场景三

TIP

前端注册,在 form 表单中填写用户名、密码、年龄等信息,点击注册提交传递给后端,后端需要解析这些数据,保存到数据库

image-20240217193233208

注:

对于不同的 HTTP 请求,需要使用不同的方式携带参数

  • GET 请求:在 URL 中以键值对传递
  • POST/PUT/PATCH/DELETE 请求:在请求体中传递

# 2、处理 URL 参数

TIP

需求:获取所有用户信息,返回一个数组

src/router/user.route.js

// 1、导入 koa-router
const Router = require("koa-router");
// 2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 定义一个内存数据库,即:定义一个全局变量,大数组
const db = [
  { id: 1001, username: "icoding", age: 22 },
  { id: 1002, username: "allen", age: 18 },
  { id: 1003, username: "david", age: 15 },
];

// 3、编写路由规则

// GET  /user 获取所有的用户信息,返回一个数组
router.get("/", (ctx) => {
  ctx.body = db;
});
router.post("/", (ctx) => {
  ctx.body = "我是创建用户页";
});

module.exports = router;

image-20240217194916802

# 2.1、通过 params 解析参数

TIP

根据 id 获取单个用户的信息,返回一个对象。通过 ctx.params 解析参数

image-20240217185817874

src/router/user.route.js

// 1、导入 koa-router
const Router = require("koa-router");
// 2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 定义一个内存数据库,即:定义一个全局变量,大数组
const db = [
  { id: 1001, username: "icoding", age: 22 },
  { id: 1002, username: "allen", age: 18 },
  { id: 1003, username: "david", age: 15 },
];

// 3、编写路由规则

// GET  /user 获取所有的用户信息,返回一个数组
router.get("/", (ctx) => {
  ctx.body = db;
});

// GET /user/:id
// 根据 id 获取单个用户的信息,返回一个对象
router.get("/:id", (ctx) => {
  // 解析 id 参数
  const id = ctx.params.id;
  // 在 db 中查找与 参数 id 匹配的用户信息,并返回该用户信息
  const res = db.filter((item) => item.id == id);

  // 如果返回结果为空,抛出 404 错误
  if (!res[0]) {
    ctx.throw(404);
  }

  // 返回对应的结果
  ctx.body = res[0];
});

router.post("/", (ctx) => {
  ctx.body = "我是创建用户页";
});

module.exports = router;

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-router.js

GIF-2024-2-17-22-35-29

# 2.2、断点调试,查看参数传递过程

TIP

在 VSCode 中先停止 node 服务,在解析 id 参数的代码行处打断点

image-20240217232907402

打开 src/koa-router.js 文件,直接按 F5 键,开启调试模式,打开调试控制台 -> 在 postman 中发起请求

image-20240217233451268

或 点击进入 “运行和调试” 窗口 -> 点击 “创建 launch.json 文件” -> 选择 “Node.js”

image-20240217233834761

点击开启调试 -> 打开调试控制台 -> 在 postman 中发起请求

image-20240217234325027

在 postman 中发起 GET 请求,输入 http://localhost:3000/user/1001

image-20240217234545387

进入调试状态,点击下一步 -> 观察参数变化

image-20240217234716038

# 3、解析键值对参数(查询字符串)

TIP

可通过 ctx.query 解析键值对参数(Querystring),ctx.queryctx.request.query 的代理

image-20240217190620209

src/router/user.route.js

// 1、导入 koa-router
const Router = require("koa-router");
// 2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 定义一个内存数据库,即:定义一个全局变量,大数组
const db = [
  { id: 1001, username: "icoding", age: 22 },
  { id: 1002, username: "allen", age: 18 },
  { id: 1003, username: "david", age: 15 },
];

// 3、编写路由规则

// GET  /user 获取所有的用户信息,返回一个数组
router.get("/", (ctx) => {
  // -------------------------
  // 通过 ctx.query 解析键值对参数,GET  /user?start=18&end=25
  const { start = 0, end = 0 } = ctx.query; // 默认值为 0

  // 如果起始值 大于 结束值时,抛出 422 错误
  if (start >= end) ctx.throw(422);

  // 按条件查询对应数据
  const res = db.filter((item) => item.age >= start && item.age <= end);
  // 判断返回结果是否为空数组
  // ctx.body = res
  res.length == 0 ? ctx.throw(404) : (ctx.body = res);
});

// GET /user/:id
// 根据 id 获取单个用户的信息,返回一个对象
router.get("/:id", (ctx) => {
  // 解析 id 参数
  const id = ctx.params.id;
  // 在 db 中查找与 参数 id 匹配的用户信息,并返回该用户信息
  const res = db.filter((item) => item.id == id);

  // 如果返回结果为空,抛出 404 错误
  if (!res[0]) {
    ctx.throw(404);
  }

  // 返回对应的结果
  ctx.body = res[0];
});

router.post("/", (ctx) => {
  ctx.body = "我是创建用户页";
});

module.exports = router;

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-router.js

image-20240218133925594

# 4、处理 body 参数

TIP

以上学习了 GET 请求解析 URL 参数的两种方式

  • 键值对:查询字符串(Querystring)可通过 ctx.query 得到
  • 动态路由传参:通过 ctx.params 得到

在 POST/PUT/PATCH/DELETE 请求:会通过请求体中传递参。即:放在 HTTP 请求报文的 body 中来传递参数

# 5、koa-body

TIP

koa 原生是不支持 body 的参数解析,会借助社区提供的中间件来实现。官方推荐如下

image-20240217193233208

①、安装 koa-body

npm i koa-body

②、注册 koa-body 中间件

// 注册 koa-body 中间件,解析请求体中的参数
const { koaBody } = require("koa-body");
// 挂载到 ctx.request.body 上
app.use(koaBody());

③、koa-body 的使用

// 通过 ctx.request.body 获取请求体中的数据
ctx.request.body;

src/koa-router.js 中, 注册 koa-body

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// --------------------

// 注册 koa-body 中间件,解析请求体中的参数
const { koaBody } = require("koa-body");
// 挂载到 ctx.request.body 上
app.use(koaBody());

// 3、导入 router 路由
const userRoute = require("./router/user.route");

// 4、注册路由中间件
// 将 原来的 router 替换成 userRoute ,同时变成链式操作
app.use(userRoute.routes()).use(userRoute.allowedMethods());

// 5、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

src/router/user.route.js 中,实现创建用户路由逻辑

// 1、导入 koa-router
const Router = require("koa-router");
// 2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 定义一个内存数据库,即:定义一个全局变量,大数组
const db = [
  { id: 1001, username: "icoding", age: 22 },
  { id: 1002, username: "allen", age: 18 },
  { id: 1003, username: "david", age: 15 },
];

// 3、编写路由规则

// GET  /user 获取所有的用户信息,返回一个数组
router.get("/", (ctx) => {
  // 通过 ctx.query 解析键值对参数,GET  /user?start=18&end=25
  const { start = 0, end = 0 } = ctx.query; // 默认值为 0

  // 如果起始值 大于 结束值时,抛出 422 错误
  if (start >= end) ctx.throw(422);

  // 按条件查询对应数据
  const res = db.filter((item) => item.age >= start && item.age <= end);
  // 判断返回结果是否为空数组
  // ctx.body = res
  res.length == 0 ? ctx.throw(404) : (ctx.body = res);
});

// GET /user/:id
// 根据 id 获取单个用户的信息,返回一个对象
router.get("/:id", (ctx) => {
  // 解析 id 参数
  const id = ctx.params.id;
  // 在 db 中查找与 参数 id 匹配的用户信息,并返回该用户信息
  const res = db.filter((item) => item.id == id);

  // 如果返回结果为空,抛出 404 错误
  if (!res[0]) {
    ctx.throw(404);
  }

  // 返回对应的结果
  ctx.body = res[0];
});

// -------------------------

// 创建用户 POST /user
router.post("/", (ctx) => {
  // 通过 ctx.request.body 获取请求体中的数据
  console.log(ctx.request.body); // { username: 'icoding', age: 18 }
  ctx.body = "我是创建用户页";
});

module.exports = router;

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-router.js

image-20240218155533624

发送 POST 请求后,VSCode 终端获取到 请求体 body 中的数据

image-20240218174351288

# 六、错误处理

TIP

对于接口编程,错误处理是非常重要的环节,通过提供更友好的提示

  • 提高错误定位的效率
  • 提高代码的稳定性 和 可靠性

错误处理:koa 自带的错误处理、原生的错误处理

# 1、原生的错误处理

TIP

一般 Koa 中的错误分为三类

  • 404:当请求的资源找不到,或者没有通过ctx.body返回时,由 koa 自动返回(不需要写任何代码)
  • 手动抛出:通过ctx.throw手动抛出
  • 500:运行时错误(代码里出现任何错误情况时)

Koa 类是继承 Emitter 类(node 内置的 EventEmitter 模块 (opens new window)),因此可以

  • 通过 emit 提交一个错误
  • 通过 on 进行统一错误处理

这是一个发布订阅的模式

src/koa-router.js 中,在所有中间件的最后面来定义错误处理

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 注册 koa-body 中间件,解析请求体中的参数
const { koaBody } = require("koa-body");
// 挂载到 ctx.request.body 上
app.use(koaBody());

// 3、导入 router 路由
const userRoute = require("./router/user.route");

// 4、注册路由中间件
// 将 原来的 router 替换成 userRoute ,同时变成链式操作
app.use(userRoute.routes()).use(userRoute.allowedMethods());

// ------------------------------

// 统一错误处理
// on 方法,监听 error 事件,可触发很多的事件,这里只监听了 error 事件
// error 为要触发的事件的自定义名称,后边通过 emit 提交时,也要叫 error 这个名称
// (err, ctx) => {} 回调函数的参数,err:emit 提交的错误信息对象;ctx:上下文对象;
app.on("error", (err, ctx) => {
  console.error(err);
  ctx.body = err;
});

// 5、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

src/router/user.route.js 中,通过 koa 原生的方式做手动错误处理

// 1、导入 koa-router
const Router = require("koa-router");
// 2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 定义一个内存数据库,即:定义一个全局变量,大数组
const db = [
  { id: 1001, username: "icoding", age: 22 },
  { id: 1002, username: "allen", age: 18 },
  { id: 1003, username: "david", age: 15 },
];

// 3、编写路由规则

// GET  /user 获取所有的用户信息,返回一个数组
router.get("/", (ctx) => {
  // 通过 ctx.query 解析键值对参数,GET  /user?start=18&end=25
  const { start = 0, end = 0 } = ctx.query; // 默认值为 0

  // 如果起始值 大于 结束值时,抛出 422 错误
  if (start >= end) ctx.throw(422);

  // 按条件查询对应数据
  const res = db.filter((item) => item.age >= start && item.age <= end);
  // 判断返回结果是否为空数组
  // ctx.body = res
  res.length == 0 ? ctx.throw(404) : (ctx.body = res);
});

// GET /user/:id
// 根据 id 获取单个用户的信息,返回一个对象
router.get("/:id", (ctx) => {
  // 解析 id 参数
  const id = ctx.params.id;
  // 在 db 中查找与 参数 id 匹配的用户信息,并返回该用户信息
  const res = db.filter((item) => item.id == id);

  // 如果返回结果为空,抛出 404 错误
  if (!res[0]) {
    ctx.throw(404);
  }

  // 返回对应的结果
  ctx.body = res[0];
});

// 创建用户 POST /user
router.post("/", (ctx) => {
  console.log(ctx.request.body); // { username: 'icoding', age: 18 }
  ctx.body = "我是创建用户页";
});

// ---------------------------

// koa 原生的错误处理(自己手动做错误处理)

// 接口:获取 id=1 的用户所发布的 cid=1 的评论信息
router.get("/:id/comment/:cid", (ctx) => {
  console.log(ctx.params);

  // 模拟运行时的错误(无法统一返回,只能报 500,该错误处理方式并不友好)
  // a.b

  if (false) {
    ctx.body = { id: 1, content: "评论内容" };
  } else {
    // 加上 return 表示:直接让该函数返回,后续操作即截止
    // 通过 emit 提交一个错误,error 名称要与 统一监听错误信息的名称保持统一
    return ctx.app.emit("error", { code: 404, message: "资源没有找到" }, ctx);
  }
});

module.exports = router;

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-router.js

image-20240219011541953

注:

以上原生的错误处理方式,存在问题

  • emit 提交错误时,书写的错误信息代码冗长、且不灵活,会出现大量的重复代码,不利于管理和维护;
  • 通过 ctx.throw() 抛出的异常,无法通过我们自定定义的统一错误处理方法所捕获到
  • 代码中的运行时错误,同样也无法被统一错误处理方法捕获到

因此,在社区为我们提供了优秀的错误处理中间件 koa-json-error,它返回的就是 JSON 格式,同时也符合 RESTful 风格的最佳实践

# 2、koa-json-error 错误处理中间件

安装 koa-json-error (opens new window) ,详细的用法可查阅官方文档

npm i koa-json-error

# 2.1、基础用法

TIP

重新复制一份 src/koa-router.js 文件,重命名为 src/koa-router-error.js

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 导入 koa-body 中间件
const { koaBody } = require("koa-body");
// 注册 koa-body 中间件,解析请求体中的参数,挂载到 ctx.request.body 上
app.use(koaBody());

// ------------------
// 使用 koa-json-error 做统一错误处理

// 导入 koa-json-error 中间件
const error = require("koa-json-error");
// 注册中间件(基础用法直接注册一个 error() 函数即可)
app.use(error());

// 3、导入 router 路由
const userRoute = require("./router/user.route");

// 4、注册路由中间件
// 将 原来的 router 替换成 userRoute ,同时变成链式操作
app.use(userRoute.routes()).use(userRoute.allowedMethods());

// 5、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

src/router/user.route.js 中,模拟错误调试

// 1、导入 koa-router
const Router = require("koa-router");
// 2、实例化 router 路由对象
// 给路由设置一个统一的前缀
const router = new Router({ prefix: "/user" });

// 定义一个内存数据库,即:定义一个全局变量,大数组
const db = [
  { id: 1001, username: "icoding", age: 22 },
  { id: 1002, username: "allen", age: 18 },
  { id: 1003, username: "david", age: 15 },
];

// 3、编写路由规则

// GET  /user 获取所有的用户信息,返回一个数组
router.get("/", (ctx) => {
  // 通过 ctx.query 解析键值对参数,GET  /user?start=18&end=25
  const { start = 0, end = 0 } = ctx.query; // 默认值为 0

  // 如果起始值 大于 结束值时,抛出 422 错误
  if (start >= end) ctx.throw(422);

  // 按条件查询对应数据
  const res = db.filter((item) => item.age >= start && item.age <= end);
  // 判断返回结果是否为空数组
  // ctx.body = res
  res.length == 0 ? ctx.throw(404) : (ctx.body = res);
});

// GET /user/:id
// 根据 id 获取单个用户的信息,返回一个对象
router.get("/:id", (ctx) => {
  // 解析 id 参数
  const id = ctx.params.id;
  // 在 db 中查找与 参数 id 匹配的用户信息,并返回该用户信息
  const res = db.filter((item) => item.id == id);

  // 如果返回结果为空,抛出 404 错误
  if (!res[0]) {
    ctx.throw(404);
  }

  // 返回对应的结果
  ctx.body = res[0];
});

// 创建用户 POST /user
router.post("/", (ctx) => {
  console.log(ctx.request.body); // { username: 'icoding', age: 18 }
  ctx.body = "我是创建用户页";
});

// ---------------------------

// 使用 koa-json-error 中间件后,模拟错误

// 接口:获取 id=1 的用户所发布的 cid=1 的评论信息
router.get("/:id/comment/:cid", (ctx) => {
  console.log(ctx.params);

  // 模拟运行时的错误(此时,就可正常返回 JSON 对象,进行统一处理)
  // a.b

  // 手动抛出异常
  ctx.throw(422, "参数格式不正确");
});

module.exports = router;

在 VSCode 的命令行终端启动 node 服务

npx nodemon .\src\koa-router-error.js

手动抛出 422 错误时,返回的错误信息(结果为 JSON 格式)

image-20240219015605678

模拟运行时错误,返回的错误信息(结果为 JSON 格式)

image-20240219015756147

注:

以上基础用法都成功的返回了 JSON 格式,但按照接口文档的规范,在 JSON 对象中会有 code、message 等一些定制的信息,koa-json-error 也是支持的

# 2.2、高级用法

TIP

  • 格式化错误信息(可根据接口规范来定制错误信息)
  • 区分生产环境 和 开发环境,开发环境 返回错误堆栈信息,生产环境不返回
  • 注:错误堆栈在生产环境中 是不能暴露给用户的(前端),非常危险。因为错误堆栈有详细的文件名称、目录结构、代码行等敏感信息,仅用于开发环境方便调试使用

src/koa-router-error.js

// 1、导入 Koa
const Koa = require("koa");

// 2、实例化 app 对象
const app = new Koa();

// 导入 koa-body 中间件
const { koaBody } = require("koa-body");
// 注册 koa-body 中间件,解析请求体中的参数,挂载到 ctx.request.body 上
app.use(koaBody());

// ------------------
// 使用 koa-json-error 做统一错误处理

// 导入 koa-json-error 中间件
const error = require("koa-json-error");
// 注册中间件(高级用法)
app.use(
  error({
    // 将默认的错误信息,重新格式化再返回
    format: (err) => {
      // code: 状态码
      // message: 错误信息
      // result:错误堆栈(这个堆栈信息在生产环境中 是不能暴露给用户的(前端),非常危险
      // 错误堆栈有详细的文件名称、目录结构、代码行等敏感信息,仅用于开发环境方便调试使用
      return { code: err.status, message: err.message, result: err.stack };
    },
    // err:原生的 error
    // obj:通过上边 format 格式化后返回的结果
    postFormat: (err, obj) => {
      // 通过解构赋值 将 result 单独提取处理
      // 剩余的部分 放到 rest 中(剩余参的用法)
      const { result, ...rest } = obj;
      // 如果是生产环境:不返回错误堆栈信息,开发环境:返回错误堆栈
      return process.env.NODE_ENV == "production" ? rest : obj;
    },
  })
);

// 3、导入 router 路由
const userRoute = require("./router/user.route");

// 4、注册路由中间件
// 将 原来的 router 替换成 userRoute ,同时变成链式操作
app.use(userRoute.routes()).use(userRoute.allowedMethods());

// 5、监听端口,启动服务
app.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

# 2.3、配置环境变量

TIP

设置环境变量,区分生产环境 和 开发环境

package.json 中,添加脚本信息通过启动 node 服务时,来做区分

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    // 开发环境
    "dev": "nodemon ./src/koa-router-error.js",
    // 生产环境
    // 配置环境变量 NODE_ENV=production 该方式在 windows下不生效
    // 需要安装 npm i cross-env 跨平台的环境变量配置,它可以让 windows、Linux、Mac 等都支持统一的配置方式
    // 在生产环境中一般用 pm2 来启动服务,这里模拟直接使用 node 来启动服务
    "prod": "cross-env NODE_ENV=production node ./src/koa-router-error.js"
  },

启动开发环境

npm run dev

image-20240219023646647

在 postman 中发送请求,查看 开发环境 中,返回的错误信息(开发环境 返回错误堆栈信息)

image-20240219023801174

启动生产环境

npm run prod

image-20240219024030363

在 postman 中发送请求,查看 生产环境 中,返回的错误信息(生产环境 不返回错误堆栈信息)

image-20240219024141839

上次更新时间: 3/3/2024, 1:15:43 AM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X