目录
- 前言
- 一,初体验
- 二,路由
- 2.1 什么是路由
- 2.2. 路由的使用
- 2.3 获取请求报文参数
- 2.4 id的通配
- 2.5 响应的设置
- 三,中间件
- 3.1 中间件概述
- 3.2 全局中间件与路由中间件的比对
- 3.3 全局中间件的使用
- 3.4 局部中间件的使用
- 3.5 静态资源中间件(内置)
- 3.6 获取请求体数据中间件(外置)
- 四,路由模块化
- 五,模板引擎
- 5.1 模板引擎
- 5.2 EJS
- 5.3 EJS与express框架的结合
- 六,express-generator
- 6.1 创建一个express项目
- 6.2 代码结构
- 6.3 url前缀
- 后记
前言
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:
二,路由
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框架的学习内容。
如果觉得有用可以点赞收藏加关注哦,后面有相关文章会发消息推送给您。