web框架Express

1.认识express

  • 简介:基于 Node.js 平台,快速、开放、极简的 Web 开发框架
  • 类似于Node.js中的内置模块http模块,专门用来创建Web服务器。

安装

首先假定你已经安装了 Node.js,接下来为你的应用创建一个目录,然后进入此目录并将其作为当前工作目录。

1
2
$ mkdir myapp
$ cd myapp

通过 npm init 命令为你的应用创建一个 package.json 文件。 欲了解 package.json 是如何起作用的,请参考 Specifics of npm’s package.json handling.

1
$ npm init

此命令将要求你输入几个参数,例如此应用的名称和版本。 你可以直接按“回车”键接受大部分默认设置即可,下面这个除外:

1
entry point: (index.js)

键入 app.js 或者你所希望的名称,这是当前应用的入口文件。如果你希望采用默认的 index.js 文件名,只需按“回车”键即可。

接下来在 myapp 目录下安装 Express 并将其保存到依赖列表中。如下:

1
$ npm install express --save

如果只是临时安装 Express,不想将它添加到依赖列表中,可执行如下命令:

1
$ npm install express --no-save

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//第一步:导入express模块
const express = require('express')
//第三步:创建一个app对象
const app = express()
const port = 3000
//发送get请求,请求路径是'/'
app.get('/', (req, res) => {
//响应了'Hello World!'
res.send('Hello World!')
})
//第二步:开启web服务器。
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

我们注意到,它的这个用法和node.js里面的http模块很相似,所以express相当于我们的http模块,但是express功能更强大。

获取请求路径中的参数

使用req.query()方法,该方法的默认值是一个空对象,当客户端携带了query参数(?age=20&name=zhangsan)时,可以获取到该参数

1
2
3
4
5
app.get('/',function(req,res){
let queryParams = req.query()
log(queryParams.age) //20
log(queryParams.name)//zhangsan
})

获取请求路径中的动态参数

语法:req.params()

通过该方法可以访问到请求路径中通过:匹配的动态参数

示例访问路径:localhost:8080/user/4

1
2
3
4
app.get('/user/:id',function(req,res){
let Params = req.params()
console.log(Params.id)//4
})

注意:id时动态参数

匹配多个动态参数的写法:user/:id/:name/:xxx...

2.基本路由

直接使用路由

路由是指确定应用程序如何响应客户端对特定端点的请求,该端点是一个 URI(或路径)和特定的 HTTP 请求方法(GET、POST 等)。

每个路由可以有一个或多个处理程序函数,这些函数在路由匹配时执行。

路由定义采用以下结构:

1
app.METHOD(PATH, HANDLER)

对参数的解释:

  • app是express的实例(对象)
  • METHOD是一个小写HTTP请求方法(post,get,put,delete)
  • PATH是所请求的资源在服务器上的路径
  • HANDLER是路由匹配时执行的函数。

举个例子

在主页上响应’Hello World!’

1
2
3
app.get('/',function(req,res){
res.send('Hello World!')
})

响应 POST请求在根路径/

1
2
3
app.post('/',function(req,res){
res.send('这是一个POST请求')
})

响应一个PUT请求在路径/user

1
2
3
app.put('/user',function(req,res){
res.send('这是一个在/user的put请求')
})

注意:

  • 按照定义的先后顺序进行匹配
  • 请求类型和请求URL同时匹配成功,才会调用对应的处理函数

模块化路由

express不建议直接将路由挂载到app上,推荐把路由抽离为单独的模块

模块化路由步骤如下

  • 创建路由模块对应的.js文件
  • 调用express.Router()函数创建路由对象
  • 向路由对象上挂载具体路由
  • 使用module.exports共享路由
  • 使用app.use()函数注册路由模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//引入express模块
const express = require('express')
//实例化对象
const router = express.Router()
//处理请求路径以及进行响应

router.get('/user/list',function(req,res){
res.send('这是一个用户表请求')
})

router.post('/app/list',function(req,res){
res.send('这是一个应用列表请求')
})
//共享路由
module.exports(router)

注册路由步骤:

  • 导入路由模块
  • 注册路由模块

这里我们用上面自定义的模块路由演示

image-20240712164808045

1
2
3
4
5
//导入路由自定义模块
const router = require('./模块化路由.js')
//注册路由模块
app.use(router)
//app.use()函数的作用是用来注册全局中间件

为路由模块添加前缀

类似于托管静态资源,为静态资源添加前缀,给路由模块添加前缀的如下

1
2
3
4
//导入路由自定义模块
const router = require('./模块化路由.js')
//注册路由模块
app.use('/api',router)

当我们为路由模块添加了前缀时,在访问时,路径变成了/api/xxx

3.托管静态资源

托管资源

为了提供诸如图像、CSS 文件和 JavaScript 文件之类的静态文件,请使用 Express 中的 express.static 内置中间件函数。

此函数特征如下:

1
express.static(root, [options])

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

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

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

1
2
3
4
5
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html

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

托管多个静态资源

如果要使用多个静态资源目录,请多次调用 express.static 中间件函数:

1
2
app.use(express.static('public'))
app.use(express.static('files'))

访问静态资源文件时,express.static 中间件函数会根据目录的添加顺序查找所需的文件。

为托管的静态资源添加前缀

语法:

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

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

1
2
3
4
5
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
http://localhost:3000/static/js/app.js
http://localhost:3000/static/images/bg.png
http://localhost:3000/static/hello.html

4.nodemon工具

nodemon会在我们的代码修改时,自动的重启服务器,极大地提高了开发效率

安装

1
npm i nodemon -g

使用

1
2
#安装nodemon后通过下面命令启动项目即可
nodemon app.js

5.中间件

简介

Express 是一个路由和中间件 Web 框架,它本身具有最少的功能:Express 应用程序本质上是一系列中间件函数调用。

简单理解

image-20240712181551659

中间件函数是可以访问请求对象 (req)、响应对象 (res) 以及应用程序请求-响应周期中的下一个中间件函数的函数。下一个中间件函数通常由名为next的变量表示。

中间件函数可以执行以下任务:

  • 执行任何代码。

  • 对请求和响应对象进行更改。

  • 结束请求-响应周期。

  • 调用堆栈中的下一个中间件函数。

如果当前中间件函数未结束请求-响应周期,则必须调用 next() 将控制权传递给下一个中间件函数。否则,请求将悬而未决。

您可以使用可选的挂载路径加载应用程序级和路由器级中间件。您还可以将一系列中间件函数一起加载,从而在挂载点创建中间件系统的子堆栈。

中间件的调用流程

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

image-20240712181917365

中间件的执行顺序和我们定义的顺序一样

中间件处理函数

Express的中间件,本质上就是一个function处理函数,他的格式如下

image-20240712182238563

在中间件函数中,必须包含next参数,但是在路由处理函数的参数中只有req和res

next函数

中间件中的流转通过next函数实现,用于多个中间件连续调用。非常重要

中间件函数例子

以下是一个简单的“Hello World” Express 应用程序示例。名为 myLogger 的函数用于打印简单的日志消息。

准备web服务器

1
2
3
4
5
6
7
8
var express = require('express')
var app = express()

app.get('/', function (req, res) {
res.send('Hello World!')
})

app.listen(3000)

定义中间件函数 myLogger

下面是一个名为“myLogger”的中间件函数的简单示例。当应用程序的请求通过该函数时,该函数只会打印“LOGGED”。中间件函数被分配给一个名为 myLogger 的变量。

1
2
3
4
5
var myLogger = function (req, res, next) {
console.log('LOGGED')
//通过next函数把流转关系转交给下一个中间件或路由
next()
}

注意请注意上面对 next() 的调用。调用此函数会调用应用中的下一个中间件函数。next() 函数不是 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三个参数。next() 函数可以命名为任意名称,但按照惯例,它始终命名为“next”。为避免混淆,请始终使用此惯例。

要加载中间件函数,请调用 app.use(),并指定中间件函数。例如,以下代码在路由挂载到根路径 (/) 之前加载 myLogger 中间件函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var express = require('express')
var app = express()

var myLogger = function (req, res, next) {
console.log('LOGGED')
next()
}
//调用app.use()来加载中间件函数,并且是全局生效的
app.use(myLogger)

app.get('/', function (req, res) {
res.send('Hello World!')
})

app.listen(3000)

每次应用程序收到请求时,它都会将消息“LOGGED”打印到终端。

中间件加载的顺序很重要:首先加载的中间件函数也会首先执行。

如果 myLogger 在路由到根路径之后加载,则请求永远不会到达它,并且应用程序不会打印“LOGGED”,因为根路径的路由处理程序会终止请求-响应循环。

中间件函数 myLogger 只是打印一条消息,然后通过调用 next() 函数将请求传递给堆栈中的下一个中间件函数。

中间件的作用

多个中间件之间,共享同一份req和res,基于特性,我们可以再上一个中间件中,统一为req或res对象添加自定义的属性或方法,供下一个中间件或路由进行使用。

image-20240712184545688

局部中间件

我们知道用app.use()方法挂载的是全局中间件,下面我们介绍一下局部中间件

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()

//定义中间件函数
function mw1 (req,res,next){
console.log('这是局部生效的中间件')
next()
}

//挂载路由
app.get('/user',mw1,function(req,res){
res.send('User页面')
})
//挂载路由
app.post('user/list',function(req,res){
res.send('userList页面')
})

//开启一个web服务器
listen(80,()=>{

})

image-20240712190200043

当我们**没有使用app.use()**定义中间件时,那他就是一个局部中间件。使用局部中间件时,想用到那个路由,就在请求方法列表中加入该中间件的名称,比如上面,中间件只对/user路由生效,而下面的不会生效。

当我们使用多个局部中间件时,只需要把多个局部中间件用,隔开即可。或者用数组的形式也可以。

image-20240712190550576

中间件的注意事项

  • 一定要在路由之前注册中间件
  • 客户端发送来的请求,可以调用多个中间件进行处理
  • 执行完中间件的业务代码之后,不要忘记调用next()函数
  • 多个中间件之间,共享req(请求对象)和res(响应对象)

6.中间件的分类

Express 应用程序可以使用以下类型的中间件:

  • 应用程序级中间件
  • 路由器级中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

应用程序级中间件

使用 app.use() 和 app.METHOD() 函数将应用程序级中间件绑定到 app 对象的实例,其中 METHOD 是中间件函数处理的请求的 HTTP 方法(例如 GET、PUT 或 POST),以小写形式表示。

此示例显示了一个没有挂载路径的中间件函数。每次应用程序收到请求时都会执行该函数。

1
2
3
4
5
6
7
var express = require('express')
var app = express()

app.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})

路由器级中间件

路由器级中间件的工作方式与应用程序级中间件相同,只是它绑定到 express.Router() 的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var express = require('express')
var app = express()
//实例化路由对象
var router = express.Router()
// 没有挂载路径的中间件函数。每次向路由器发出请求时都会执行此代码
router.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})
//挂载到/user/:id 路径的处理程序,用于呈现特殊页面
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id)
res.render('special')
})

错误处理中间件

错误处理中间件始终需要四个参数。您必须提供四个参数来将其标识为错误处理中间件函数。即使您不需要使用下一个对象,也必须指定它以维护签名。否则,下一个对象将被解释为常规中间件,并且无法处理错误。

定义错误处理中间件函数的方式与其他中间件函数相同,只是使用四个参数而不是三个,具体来说是使用 (err, req, res, next):

1
2
3
4
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})

内置中间件

Express 具有以下内置中间件功能:

express.static 提供静态资产,例如 HTML 文件、图像等。
express.json 使用 JSON 负载解析传入请求。注意:适用于 Express 4.16.0+

1
2
//使用express.json中间件,将数据挂载req.body属性上
app.use(express.json())

express.urlencoded 使用 URL 编码负载解析传入请求(application/x-www-form-urlencoded格式数据)。注意:适用于 Express 4.16.0+

1
2
//使用express.urlencoded中间件,将数据挂载req.body属性上
app.use(express.urlencoded({extended:false}))

第三方中间件

使用第三方中间件为 Express 应用添加功能。

安装所需功能的 Node.js 模块,然后在应用程序级别或路由器级别将其加载到您的应用中。

以下示例说明了如何安装和加载 cookie 解析中间件函数 cookie-parser。

1
$ npm install cookie-parser
1
2
3
4
5
6
7
var express = require('express')
var app = express()
//通过require方式来导入
var cookieParser = require('cookie-parser')

// 挂载到app上
app.use(cookieParser())

7.自定义中间件

1.实现步骤

image-20240713130944117

2.监听req的data事件

image-20240713131105993

1
2
3
4
5
6
7
8
app.use((req,res,next)=>{
//定义一个str字符,存储客户端发送数据
let str =''
//监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
})

3.监听req的end事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.use((req,res,next)=>{
//定义一个str字符,存储客户端发送数据
let str =''
//监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//监听req的end事件
req.on('end',function(){
//该str存储的就是完整的客户端数据
console.log(str)
})
})

app.post('/user',(req,res)=>{
res.send('ok!')
})

4.使用querystring模块解析请求数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express')
const app = express()
//搭配node.js中的querystring模块
const qs = require('querystring')
app.use((req,res,next)=>{
//定义一个str字符,存储客户端发送数据
let str =''
//监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//监听req的end事件
req.on('end',function(){
//该str存储的就是完整的客户端数据
//通过qs模块解析数据
console.log(qs.parse(str)
})
})

app.post('/user',(req,res)=>{
res.send(req.body)
})

5.将解析出来的数据对象挂载到req的body属性上

1
2
3
4
5
6
7
8
req.on('end',function(){
//该str存储的就是完整的客户端数据
//通过qs模块解析数据
let bodyObj =qs.parse(str)
req.body = bodyObj
next()
})
})

6.将自定义中间件封装为模块

新建一个custom-body-parser.js文件

写入以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//搭配node.js中的querystring模块
const qs = require('querystring')
const bodyParser = (req,res,next)=>{
//定义一个str字符,存储客户端发送数据
let str =''
//监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//监听req的end事件
req.on('end',function(){
//该str存储的就是完整的客户端数据
//通过qs模块解析数据
let bodyObj =qs.parse(str)
req.body = bodyObj
next();
})
}

//通过module.exports暴露该模块
module.exports = bodyParser

1
2
3
4
//在需要用解析功能的js文件中导入js文件即可
const bp = require('./custom-body-parser.js')
//注册自定义的中间件模块
app.use(bp)

完整代码

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
const express = require('express')
const app = express()
//搭配node.js中的querystring模块
const qs = require('querystring')
app.use((req,res,next)=>{
//定义一个str字符,存储客户端发送数据
let str =''
//监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//监听req的end事件
req.on('end',function(){
//该str存储的就是完整的客户端数据
//通过qs模块解析数据
let bodyObj =qs.parse(str)
req.body = bodyObj
next();
})
})

app.post('/user',(req,res)=>{
res.send(req.body)
})

8.基于express写接口

写一个自定义路由模块

1
2
3
4
5
6
const express = require('express')
//实例化路由对象
const apiRouter = express.Router()

//暴露
module.exports = apiRouter

导入模块

1
2
3
4
5
6
7
8
9
10
11
12
13
//导入路由模块
const Router = require('./apiRouter.js')
app.get('/get'(req,res)=>{
//通过req.query()方法拿到客户端携带的查询参数
const query = req.query()
res.send({
status:0 // 0表示成功 1表示失败
msg:'Get请求成功'
data:query
})
})
//注册全局
app.use('/api',Router)

上面这个就是get接口。

下面来举例post接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//解析post请求的数据,详见目录内置中间件
app.use(express.urlencoded({extends:false}))
//导入路由模块
const Router = require('./apiRouter.js')
app.get('/post'(req,res)=>{
//通过req.query()方法拿到客户端携带的查询参数
const body = req.body()
res.send({
status:0 // 0表示成功 1表示失败
msg:'Get请求成功'
data:body
})
})

//注册全局
app.use('/api',Router)