Express框架从入门到如土

前言

Express框架是一个基于Node.js平台的极简的、灵活的web开发框架。封装了Nodejs的http模块。我们可以通过Express框架来实现很多功能。

学习本篇文章需要有前端三件套以及发送请求相关的基础,还要有Nodejs的相关基础(http模块)。这些在笔者的专栏中都有涉及到,可以查看:

前后端交互知识储备
Nodejs

话不多说,开始学习。

一,初体验

首先,需要进行包管理初始化:

npm i init -y

接着需要下载express包:

npm i express

接着,我们来写如下代码:

//导入express
const express = require('express')
//创建实例对象
const app = express()

//创建路由
app.get('/home', (req, res) => {
    
    
    res.end('hello express')
})

//监听端口
app.listen(3000, () => {
    
    
    console.log('服务已经启动,端口3000正在监听中...')
})

在写完下面代码后终端运行:

在这里插入图片描述
打开127.0.0.1:3000/home:
在这里插入图片描述

扫描二维码关注公众号,回复: 15436757 查看本文章

二,路由

2.1 什么是路由

官方定义:路由确定了应用程序如何响应客户端对待特定端点的请求。

2.2. 路由的使用

路由的组成有:请求方法、路径、回调函数。

express提供了一系列方法,可以很方便的使用路由:

app.<method>(path.callback)

其中,method可以是get,也可也是post,和http模块非常类似。

这里有一个特例:

app.get('/', (req, res) => {
    
    
    res.end('home')
})

发送请求,面上为空,实际上报文中是一个/。

访问首页一般都不会主动设置路径,都由上面这个路由规则进行响应。

在这里插入图片描述
还有一个路由比较特殊:

app.get('*', (req, res) => {
    
    
    res.end('404 not found')
})

在这个路由规则中,*代表着,除了上面写的其他路由规则之外的请求,都会返回404。

另外再说一个比较特殊的,不过是方法上的特殊:

app.all('/text', (req, res) => {
    
    
    res.end(test)
})

上面这串代码,无论是get还是post都会返回test。

2.3 获取请求报文参数

框架封装了一些API来方便获取请求报文中的数据,并且兼容原生HTTp模块的获取方式。

我们先来看一下原生的操作:

const express = require('express')

const app = express()

app.get('/request', (req, res) => {
    
    
    // 原生操作
    console.log(req.method)//获取请求方法
    console.log(req.url)//获取请求url
    console.log(req.httpVersion)//获取http版本
    console.log(req.header)//获取请求头部
    res.end('hello express')
})

app.listen(3000, () => {
    
    
    console.log('服务已经启动,端口3000正在监听中......')
})

上面这些方法在express框架中依旧可以使用。是因为express是兼容http模块的。

我们来看一下运行结果:
在这里插入图片描述
接着我们来看看用express框架下该如何获取请求报文参数:

    console.log(req.path)//获取路径
    console.log(req.query)//获取查询字符串
    console.log(req.ip)//获取ip
    console.log(req.get('host'))//获取某个请求头

这次发送一个携带参数的请求:
在这里插入图片描述
在控制台显示的结果是:
在这里插入图片描述

2.4 id的通配

这里从前端的角度来说。比如我要做一个产品页面。

产品页面中会有很多个产品,每个产品点开后的展示格式是一样的,但是内容是不一样的。每个产品在数据库里面有个id。

那我们在发送请求申请产品的时候是不是要把每个产品的id对应的路由都写到呢?非也。

我们可以写一个id的通配,实现每个id发送请求都可以响应相应数据:

app.get('/:id.html', (req, res) => {
    
    
    //获取路由参数
    console.log(req.params.id)//获取id
    res.send('产品')
})

无论请求后面是哪个参数,都可以获取相应的页面:
在这里插入图片描述

2.5 响应的设置

原生http的响应在express框架中依然适用。

我们来看一下原生跟响应有关的设置:

app.get('/response', (req, res) => {
    
    
    // 原生响应
    res.statusCode = 400;//状态码
    res.statusMessage = 'love'//响应信息
    res.setHeader('xxx', 'yyy')//响应头
    res.write('hello express')//设置响应体1
    res.end('response')//设置响应体2
    res.end('response')
})

接着看一下express中与响应有关的配置:

    //express响应
    res.status(500);//状态码
    res.set('aaa', 'bbb')//响应头
    ResizeObserver.send('你好')//响应体
    // 其他响应
    res.redirect('http://baidu.com')//路由重定向

三,中间件

3.1 中间件概述

中间件的本质是一个回调函数。作用是封装一些公共操作,简化代码。

中间件函数可以像路由一样,访问请求对象request和响应对象response。

中间件的作用:使用函数封装公共操作,简化代码。

中间件的类型分为两种:全局中间件和路由中间件。

3.2 全局中间件与路由中间件的比对

这里举一个例子。我们去火车站坐车,大概会有两次检票。第一次是进候车厅的时候我们需要扫身份证进行检票,在这个过程中,不同车次的人,只要在这一天有火车可做,都可以通过候车厅的检票口进入候车厅。

当我们进入候车厅后,时间到了,我们再次检票。这一次检票是在检票口,检票通过后就可以去往各自的车次。检票的时候,是同一车次的人一起检票。而不是所有人一拥而上。

我们可以把第一次检票看作全局中间件;把第二次检票看作路由中间件。

全局中间件一旦启用,在每一个请求发起时都会用到;而路由中间件一旦启用,相应路由在请求发起的时候会用到。

以上是全局中间件与路由中间件的区别。

3.3 全局中间件的使用

说一个需求,我们来实现。首先看以下路由规则:

app.get('/home', (req, res) => {
    
    
    res.send('前台首页')
})

app.get('/admin', (req, res) => {
    
    
    res.send('后台首页')
})

app.all('*', (req, res) => {
    
    
    res.send('<h1>404 Not Found</h1>')
})

现在我们来说需求:在每一个请求发起的时候,在一个文件中记录请求的的url和ip。

以下是分析:
既然是每个请求发起都要记录,那么每个路由被请求都需要有响应处理。所以是一个全局中间件。之前也说过全局中间件是一个函数。所以我们可以把这个函数定义在外面:

//声明中间件函数
function recordMiddleware(req, res, next) {
    
    

}
app.use(recordMiddleware)

app.get('/home', (req, res) => {
    
    
    res.send('前台首页')
})

app.get('/admin', (req, res) => {
    
    
    res.send('后台首页')
})

app.all('*', (req, res) => {
    
    
    res.send('<h1>404 Not Found</h1>')
})

==这个函数一共有三个参数:req,res,next。==前面也说过,中间件函数可以获取到路由的req,res。另外还要多一个参数:==next。==这个next参数是函数类型。

next参数,我们可以这么理解:当这个中间件函数调用完,才会调用next。调用next之后,会继续调用路由或者下一个中间件。

接着我们来看一下上面这个需求的完整代码:

const express = require('express')
const app = express();
const fs = require('fs')
const path = require('path')

//声明中间件函数
function recordMiddleware(req, res, next) {
    
    
    let {
    
    url, ip} = req;
    fs.appendFileSync(path.resolve(__dirname, './assess.log'), `${
      
      url} ${
      
      ip}\r\n`)
    next()
}
app.use(recordMiddleware)

app.get('/home', (req, res) => {
    
    
    res.send('前台首页')
})

app.get('/admin', (req, res) => {
    
    
    res.send('后台首页')
})

app.all('*', (req, res) => {
    
    
    res.send('<h1>404 Not Found</h1>')
})
app.listen(3000, () => {
    
    
    console.log('服务器已经启动,端口3000正在监听...')
})

上面这段代码,我来讲一下中间件函数的内部,主要是跟nodejs相关的。如果想单纯了解中间件也可也跳过。

在这段代码中,我们引入了fs和path模块。首先利用解构赋值获取到了req(请求报文)中的url和ip。其次,我们利用fs模块中的appendFileSync,这个api的作用是利用fs模块在文件中写东西,由于后面加了Sync这个后缀,所以是同步的,(一般情况下异步会有个回调函数,同步就没有)。再说说这个在文件中写东西的函数内部,内部利用path模块,resove函数是拼接字符串,第一个参数是该文件(写东西)的地址,第二个参数是写进去的内容。内容用到了字符串变量的操作。把之前获得的url和ip写进去。

最后调用next,证明上面的已经执行完毕,可以接着执行中间件或者路由。

==需要注意的是,全局中间件需要app.use一下,相当于是注册全局中间件。注册后就可以正常使用了。==完整代码在上面,有兴趣可以复制粘贴玩一下。

最后要说的是,全局中间件可以调用多个。

以上就是全局中间件的相关讲解。

3.4 局部中间件的使用

先说需求:针对/admin和/setting的请求,要求URL携带code=521参数,入围携带提示【暗号错误】。

先看基础代码:

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

app.get('/admin', (req, res) => {
    
    
    res.end('12321')
})

app.get('/setting', (req, res) => {
    
    
    res.end('12321')
})

app.listen(3000, () => {
    
    
    console.log('服务器已启用...')
}) 

接着我们来分析一下这个需求:这个需求是只针对个别路由的,所以此时,我们要选择使用局部中间件。通过局部中间件获取url并判断里面的code参数是否是521。如果是521,那么我们可以继续执行路由规则;如果不是,则返回暗号错误。

下面我们来看一下代码:

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

//声明中间件
let checkCodeMiddleware = (req, res, next) => {
    
    
    if(req.query.code === '521') {
    
    
        next();
    } else {
    
    
        res.send('暗号错误')
    }
}

app.get('/admin', checkCodeMiddleware, (req, res) => {
    
    
    res.end('12321')
})

app.get('/setting', checkCodeMiddleware, (req, res) => {
    
    
    res.end('12321')
})

app.listen(3000, () => {
    
    
    console.log('服务器已启用...')
}) 

==这个地方需要注意的是,局部中间件的注册,是在app后面调用中,以参数的形式传递的。==为了防止大家看不懂,我这里用一张图来说明下这个代码顺序:
在这里插入图片描述
看上图,当我们发送一个请求,会先判断请求的url,选择响应路由,这是第一步;接着,第二步是执行中间件,就拿上面这个例子来说,如果code是521,则执行next(),next之后就是执行下面的路由规则啦,就是第三步,执行res.end;如果不是,那就是返回’暗号错误’。

3.5 静态资源中间件(内置)

静态资源中间件是express框架中的一个内置的中间件,用于挂载静态资源,把一些静态资源挂在在网页上,正确的url访问后就可以显示出来。

接着我们来复习一下静态资源和静态资源目录这两个概念。

静态资源是指我们在写代码的时候用到的html,css,js,图片等,这些都是静态资源。

静态资源目录可以理解为网站的根目录。

接着看一下静态资源中间件的使用。既然是静态资源的挂载,所以我们先建一个静态资源目录,public,内部有一个html文件作为静态资源:
在这里插入图片描述

在这里插入图片描述
现在我们来使用内置的静态资源中间件:

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

// 静态资源的中间件注册
app.use(express.static(__dirname + '/public'));

app.get('/admin', (req, res) => {
    
    
    res.send('222')
})

app.listen(3000, () => {
    
    
    console.log('服务器已经启动...')
})

接着,就已经把静态资源挂载到我们电脑自带的服务器上了:
在这里插入图片描述

在这里插入图片描述
使用期间的注意事项:
1.index.html文件是默认打开的资源;
2.如果静态资源与路由规则同时匹配(‘/’),谁先匹配谁就响应(跟代码顺序有关);
3.路由响应动态资源,静态资源中间件响应静态资源。

3.6 获取请求体数据中间件(外置)

这个中间件是别人写好了的,并且不是内置,所以如果我们要用,需要安装和导入。

这个中间件名为border-parser。

安装导入:
在这里插入图片描述
导入border-parser包:
在这里插入图片描述
中间件使用方式:这里有两种使用方式,一种是作为全局中间件;一种是作为路由中间件。这里更推荐路由中间件,并不是每个请求都需要这个中间件来处理,使用路由中间件(局部中间件)使得项目运行效率更高。

接着是该中间件的使用,该中间件的使用有两种方式:

//解析 JSON 格式的请求体的中间件
const jonParser = bodyParser.json()
//解析 queryString 格式请求体的中间件
const urlencodeParser = bodyParser.urlencoded({
    
     extended: false });

如何看使用哪种方式获得请求体呢?在请求的载荷中:
在这里插入图片描述
可以看出,是querystring格式的。

如果我们要用,我们可以放到相对应位置去。

请看以下完整版代码及注释:

const express = require('express')
// 导入包
const bodyParser = require('body-parser')
const app = express()

// 使用body-parser中间件
//解析 JSON 格式的请求体的中间件
const jonParser = bodyParser.json()
//解析 queryString 格式请求体的中间件
const urlencodeParser = bodyParser.urlencoded({
    
     extended: false });

app.get('/login', (req, res) => {
    
    
    // 响应文件内容
    res.sendFile(__dirname + '/10._form.html')
})

app.post('/login', urlencodeParser, (req, res) => {
    
    
    console.log(req.body)//多一个body属性用来存放请求体
    res.send('获取用户的数据')
})

app.listen(3000, () => {
    
    
    console.log('服务器已启动...')
})

表单页面代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录页面</title>
</head>
<body>
    <form action="http://127.0.0.1:3000/login" method="post">
        用户名:<input type="text" name="username"><br/>
        密码: <input type="password" name="password">
        <button>登录</button>
    </form>
</body>
</html>

最后结果:
在这里插入图片描述
这里还有一个中间件的应用,感兴趣可以查看:
一文带你搞懂防盗链

四,路由模块化

模块化编程,增加了代码的复用性,提高了每一块代码的独立性。

路由模块化,可以简单理解为,拆路由

接下来,我以一个例子来讲解路由的模块化。

这是一个完整的代码,有前台有后台,现在我们把这个代码的前后台拆分一下:

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

//前台
app.get('/home', (req, res) => {
    
    
    res.send('前台首页')
})
app.get('/search', (req, res) => {
    
    
    res.send('内容搜索')
})

//后台
app.get('/admin', (req, res) => {
    
    
    res.send('后台首页')
})

app.get('/setting', (req, res) => {
    
    
    res.send('设置页面')
})

app.all('*', (req, res) => {
    
    
    res.send('<h1>404 Not Found</h1>')
})

app.listen(3000, () => {
    
    
    console.log('服务器已经运行...')
})

我们先来拆分前台路由,新建一个文件after存放内容:
在这里插入图片描述
在after中导入express并引入router:

//导入express
const express = require('express')
//创建路由对象
const router = express.Router()

接着把跟前台有关的路由放进文件并且把app改为router:

router.get('/home', (req, res) => {
    
    
    res.send('前台首页')
})
router.get('/search', (req, res) => {
    
    
    res.send('内容搜索')
})

最后再暴露出去,下面是整体代码:

//导入express
const express = require('express')
//创建路由对象
const router = express.Router()
//创建路由规则
router.get('/home', (req, res) => {
    
    
    res.send('前台首页')
})
router.get('/search', (req, res) => {
    
    
    res.send('内容搜索')
})

//暴露router
module.exports = router;

但是还没结束,还要在all中引入并注册才能够正常使用:

const express = require('express')
// 导入前台
const homeRouter = require('./after.js')
const app = express()
//注册
app.use(homeRouter)

这样以后才可以正常使用:
在这里插入图片描述
然后是后台,直接放代码,不再赘述:

const express = require('express')
const router = express.Router()
router.get('/admin', (req, res) => {
    
    
    res.send('后台首页')
})

router.get('/setting', (req, res) => {
    
    
    res.send('设置页面')
})

module.exports = router

这是整体代码:

const express = require('express')
// 导入前台
const homeRouter = require('./after.js')
// 导入后台
const adminRouter = require('./before.js')
const app = express()
//注册
app.use(homeRouter).use(adminRouter)


app.all('*', (req, res) => {
    
    
    res.send('<h1>404 Not Found</h1>')
})

app.listen(3000, () => {
    
    
    console.log('服务器已经运行...')
})

五,模板引擎

5.1 模板引擎

模板引擎是分离用户界面和业务数据的一种技术。(用于分离html与js的)

5.2 EJS

EJS是一种高级的模板引擎。

官网:官网
中文站:中文站

文档很清楚,有需要可参考。

这里我来总结下,在EJS中有一个地方非常关键,那就是

<% %>

这个符号内部是js代码,而这个符号的外部是html的结构。

这个是我总结出来的一个点。可以方便大家学习。

5.3 EJS与express框架的结合

可参考如下代码及注释:

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

//1.设置模板引擎,模板引擎有很多种,所以要设置是哪一种,我们用的ejs
app.set('view engine', 'ejs');
// 2.设置模板文件(具有模板语法内容的文件)存放位置,模板文件一定是后缀为ejs的,不然没办法找到
app.set('views', path.resolve(__dirname, './express'))

//3.render方法进行响应
app.get('/home', (req, res) => {
    
    
    // res.render('模板的文件名', '数据')
    let title = '123'
    res.render('view', {
    
    title})
})

app.listen(3000, () => {
    
    
    console.log('已经在服务器的 3000端口 运行...')
})

六,express-generator

6.1 创建一个express项目

在前面的内容中,express是我们手写出来的。而express-generator这个工具可以帮我们创建一个express框架的标准结构。一些基本的东西我们就没必要再写。

使用这个工具,首先需要安装:

npm i express-generator -g

安装之后,利用名字可以创建一个express项目:

express -h '文件夹名'

初始化的项目结构是这样的:
在这里插入图片描述
接着来说一下项目运行,项目在运行前,需要下载一些初始化:

npm i

接着是运行:
在这里插入图片描述
在上面这个文件中已经说明,运行的指令是:

npm start

在做完上面这些后,我们可以看一看效果:
在这里插入图片描述
代表运行成功。

6.2 代码结构

app.js是项目主文件;
views目录用于存放页面文件;
routes目录用于存放路由文件;
public用于存放静态文件;
bin中的www是项目的启动文件;

6.3 url前缀

在app.js代码中,有这样一段:

var usersRouter = require('./routes/users');
app.use('/users', usersRouter);

第二个use,对应的是routes中的usersRouter,我们再看看usersRouter的内容:

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
    
    
  res.send('respond with a resource');
});

module.exports = router;

在这段代码中,如果单看get请求,会发现只是个斜杠。但是这个斜杠真的可以显示出send的内容么?我们试一下:
在这里插入图片描述
不行。

原因:需要加前缀,前缀是users:

app.use('/users', usersRouter);

加前缀后可以返回相应内容:
在这里插入图片描述

后记

以上就是express框架的学习内容。

如果觉得有用可以点赞收藏加关注哦,后面有相关文章会发消息推送给您。

猜你喜欢

转载自blog.csdn.net/zxdznyy/article/details/130808126