Node.js——Express和中间件 | 青训营笔记

164 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天

Express和中间件

Express(介绍和基本操作)

Express是一个第三方库,作用是可以替代http模块使用。

Express 下载 npm i express

基本操作

1.创建基本的web服务器


2.监听客户端的get请求(app.get())

3.监听客户端的post请求(aap.post())

4.把响应的内容放回给客户端(res.send())

5.获取url中携带的查询参数(res.query)

6.获取url中的动态参数(req.params)

Express托管静态资源

1. express.static()

Express提供了一个非常好用的函数, 叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,

例如,通过如下代码就可以将public下的图片、CSS 文件、JavaScript 文件对外开放访问了:

app.use(express.static('public'))

如果有多个访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件:

app.use(express.static('public'))
app.use(express.static('.file'))

现在,你就可以访问public中的所有文件了,例如:

http://localhost:3000/images/bg.jpg

http://localhost:3000/css/style.css

http://localhost:3000/js/login.js

注意: Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。

因此,存放静态文件的目名不会出现在URL中。

举个例子:

我们的clock目录下为这样 ,这样我们直接访问http://127.0.0.1/index.html,则可以看到我们想要暴露出去的静态文件。

const express = require('express')
const app = express()

// 在这里,调用 express.static() 方法,快速的对外提供静态资源
app.use(express.static('./clock'))

app.listen(80, () => {
  console.log('express server running at http://127.0.0.1')
})

如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:

app.use('/public', express.static('./public'))

现在,你就可以通过带有/public前缀地址来访问public绿中的文件了:

http://localhost:3000/public/images/kittenjpg

http://localhost:3000/public/css/style.css

http://localhost:3000/public/js/app.js .

Express路由

在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。

Express中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数。

基本用法:

const express = require('express')
const app = express()

// 挂载路由
app.get('/', (req, res) => {
  res.send('hello world.')
})
app.post('/', (req, res) => {
  res.send('Post Request.')
})

app.listen(80, () => {
  console.log('http://127.0.0.1')
})

但是一般我们实际开发时会有很多路由,因此我们应该学习下模块化路由的用法,操作起来很简单。

// 这是路由模块
// 1. 导入 express
const express = require('express')
// 2. 创建路由对象
const router = express.Router()

// 3. 挂载具体的路由
router.get('/user/list', (req, res) => {
  res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
  res.send('Add new user.')
})

// 4. 向外导出路由对象
module.exports = router
const express = require('express')
const app = express()

// 1. 导入路由模块
const router = require('./03.router')
// 2. 注册路由模块

//调用路由,在这里我们不仅可以直接调用路由,并且可以给路由中模块tian'jia前缀,
app.use('/api', router)

// 注意: app.use() 函数的作用,就是来注册全局中间件

app.listen(80, () => {
  console.log('http://127.0.0.1')
})

中间件

在之前的express.static或者刚刚的路由中,我们都频繁的使用了app.use()这个api,这个api的作用,就是来注册全局中间件。

首先我们来介绍一下什么是中间件

中间件的概念

中间件(Middleware ),特指业务 流程的中间处理环节。

中间件的调用流程

当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。

而在我们express中,中间件本质就是一个处理东西的函数。

在中间件动数的形参列表中,必须含next参数。而路由处理函数只包含req和res。

因此我们在客户端发送一个请求给后端时,这个请求进入中间件会经历一个个中间件函数进行处理,每个中间件函数之间用next参数传递。最后传给路由,路由在响应给客户端。(注意路由没有next参数,所有路由必定是要放在最后一个函数)

用一个简单的例子来理解中间件函数

const express = require('express')
const app = express()

//  写法1 定义一个最简单的中间件函数
// const mw = function (req, res, next) {
//   console.log('这是最简单的中间件函数')
//   // 把流转关系,转交给下一个中间件或路由
//   next()
// }

// // 将 mw 注册为全局生效的中间件
// app.use(mw)

// 写法2  这是定义全局中间件的简化形式
app.use((req, res, next) => {
  console.log('这是最简单的中间件函数')
  next()
})

app.get('/', (req, res) => {
  console.log('调用了 / 这个路由')
  res.send('Home page.')
})
app.get('/user', (req, res) => {
  console.log('调用了 /user 这个路由')
  res.send('User page.')
})

app.listen(80, () => {
  console.log('http://127.0.0.1')
})

中间件的作用

多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添

加自定义的属性或方法,供下游的中间件或路由进行使用。

const express = require('express')
const app = express()

// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
  // 获取到请求到达服务器的时间
  const time = Date.now()
  // 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由
  req.startTime = time
  next()
})

app.get('/', (req, res) => {
  res.send('Home page.' + req.startTime)
})
app.get('/user', (req, res) => {
  res.send('User page.' + req.startTime)
})

app.listen(80, () => {
  console.log('http://127.0.0.1')
})

全局中间件和局部中间件

全局中间件:可以使用app.use(连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行

定义全局中间件案例:

const express = require('express')
const app = express()

// 定义第一个全局中间件
app.use((req, res, next) => {
  console.log('调用了第1个全局中间件')
  next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
  console.log('调用了第2个全局中间件')
  next()
})

// 定义一个路由
app.get('/user', (req, res) => {
  res.send('User page.')
})

app.listen(80, () => {
  console.log('http://127.0.0.1')
})

局部中间件: 不使用app.use()定义的中间件,叫做局部生效的中间件,

定义局部中间件案例:

// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

// 1. 定义中间件函数
const mw1 = (req, res, next) => {
  console.log('调用了第一个局部生效的中间件')
  next()
}

const mw2 = (req, res, next) => {
  console.log('调用了第二个局部生效的中间件')
  next()
}

// 2. 创建路由
app.get('/', [mw1, mw2], (req, res) => {
  res.send('Home page.')
})
app.get('/user', (req, res) => {
  res.send('User page.')
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1')
})

中间件的分类

1.应用型中间件:

通过app.use0或app.get(或app.post() ,绑定到app实例上的中间件,叫做应用级别的中间件,代码示例如下:

2.路由型中间件

绑定到express. Router()实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到app实例上,路由级别中间件绑定到router实例上。

3.错误型中间件

错误级别中间件的作用:专[ ]用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。

格式:错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err, req, res, next)。

案例:

// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

// 1. 定义路由
app.get('/', (req, res) => {
  // 1.1 人为的制造错误
  throw new Error('服务器内部发生了错误!')
  res.send('Home page.')
})

// 2. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
  console.log('发生了错误!' + err.message)
  res.send('Error:' + err.message)
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1')
})

注意:错误型的中间件必须放在其它中间件(包括路由)后面,才能检测出错误。


4.Express内置中间件

自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express 项目的开发效率和体验:

express.static快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)

expressjson解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)

express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)

// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))

其它

req中常用属性:

req.query获取url中携带的查询参数

req.params获取url中的动态参数

req.body获取请求体中包含的 url-encoded 格式的数据

res中常用的属性:

res.send调用 res.send() 方法,向客户端响应结果