1. 初始Express
1.1 Express 简介
Express的中文官网:link
1.2 Express 的基本使用
代码如下:
// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
res.send({ name: 'theshy', age: 20, gender: '男'})
})
app.post('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串
res.send('请求成功!')
})
// 3. 启动 web 服务器
app.listen(880, () => {
console.log('express server running at http://127.0.0.1')
})
演示:(验证服务器是否可以反应)
演示:
综合代码如下:
// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
res.send({ name: 'theshy', age: 20, gender: '男'})
})
app.post('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串
res.send('请求成功!')
})
app.get('/', (req, res) => {
// 通过 req.query 可以获取到客户端发送过来的 查询参数
// 注意:默认情况下,req.query 是一个空对象
console.log(req.query)
res.send(req.query)
})
// 注意:这里的 :id 是一个动态的参数
app.get('/user/:id', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认也是一个空对象
console.log(req.params)
res.send(req.params)
})
// 3. 启动 web 服务器
app.listen(880, () => {
console.log('express server running at http://127.0.0.1')
})
1.3 托管静态资源
演示:
演示:(express.static()函数会按代码顺序来查找所需文件)
情况1
:按此代码,浏览器打开是clock文件夹下的index.html文件,因为clock的代码排在前面,优先查找了clock文件夹里的index.html文件。
情况2
:调换顺序,把files文件夹代码放到上面后,同样的地址,浏览器打开是files文件夹下的index.html文件。
演示:给files文件夹挂在路径前缀,这样默认index.html地址(
http://127.0.0.1:880/index.html
)就会跳转到clock(时钟案例)的index.html,而只有在index.html前面加上files路径前缀(http://127.0.0.1:880/files/index.html
)才会跳到files文件夹里的index.html(显示abc)。
1.4 nodemon(工具)
nodemon链接:link
有了nodemon这个工具,就不需要在每次修改代码之后,繁琐地关闭再重启服务器了。以后启动服务器文件使用nodemon + js文件
而不再是node + js文件
演示:
使用nodemon启动服务器,它将监听代码的变化,具有自动重启更新项目的效果。
2. Express路由
2.1 路由的概念
METHOD: 请求的类型
PATH: 请求的 URL 地址
HANDLER: 处理函数
2.2 路由的使用
值得一提的是,实际开发中并不会把路由挂载在app上,因为随着代码量的增长,会挂载越来越多的路由,文件的体积会越来越大并且不方便管理。
演示:
先进行初始化操作npm init -y
,之后在当前项目文件夹下安装express
(版本为4.17.1)
编写完app代码后启动服务器,使用Postman进行测试
// 这是路由模块,这样别人require(./03-router.js)得到的就是这个路由对象,可以直接使用
// 1. 导入 express
const express = require ('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载具体的路由
router.get('/user/list', () => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})
// 4. 向外导出路由对象
module.exports = router
演示:
(03-router.js是自己定义好的路由模块,可以在02-模块化路由.js中拿来使用)
测试:
nodemon运行文件启动服务器
演示:
测试:
添加前缀之后,以后访问都需要/user/list或/user/add都需要在/前面加api了。
3. Express中间件
3.1 中间件的概念
3.2 Express中间件的初体验
3.2.1 定义中间件函数
演示:
3.2.2 全局生效的中间件
演示:
先处理唯一的中间件函数mw,处理完就通过next流转给下一个中间件,但是没有下一个中间件了,顺延给路由,调用路由。
3.2.3 定义全局中间件的简化形式
轻松改写:
3.2.4 中间件的作用
演示:
想在每个中间件里添加一个获取请求到达服务器时间的功能,但是给每个中间件这样添加很麻烦,可以直接在上游的中间件中添加自定义的方法
修改后:(使用中间件共享req的特性)
3.2.5 定义多个
全局中间件
3.2.6 局部生效的中间件
演示:
访问http://127.0.0.1:880/
因为路由调用了mw1
中间件所以终端会打印文字调用了局部生效的中间件
,而访问http://127.0.0.1:880/user
时因为其没有调用mw1
,所以不会调用中间件mw1
3.2.7 定义多个局部中间件
演示:
Postman访问地址1:
终端结果1:
Postman访问地址2:
终端结果2:
因为/user没有调用中间件,所以终端不会执行中间件函数(不会打印结果)
3.2.8 了解中间件的5个使用注意事项
关于第一点
有一个例外
,即:如果是错误级别的中间件
,那必须注册在所有路由之后
。
3.3 中间件的分类
3.3.1 应用级别的中间件
3.3.2 路由级别的中间件
3.3.3 错误级别的中间件
没有错误级别的中间件时:
终端和Postman都会崩溃报错:(所以定义错误级别中间件防止崩溃时是很有必要的
)
注意
:错误级别的中间件必须注册在所有路由之后
,否则项目会崩溃
。
定义错误级别中间件之后:(成功防止了异常崩溃的情况)
3.3.4 Express内置的中间件
express.static中间件没有兼容性,在任何版本中都能使用
新知识:(解释以下操作)
通过Post的方式,请求http://127.0.0.1:880/user
这个地址,通过body方式发送JSON格式的请求体数据
(express.json中间体的使用)
演示
:
关键就是app.use(express.json())
这个中间件起到了解析数据的作用
有这个中间件才能解析客户端发送过来的请求数据
成功拿到请求体数据{name: 'theshy', age: 20}
(express.urlencoded中间体的使用)
演示
如下:
点击Send之后,终端结果如下:
3.3.5 第三方的中间件
body-parser中间件的注册方法跟express.urlencoded近似
,因为express.urlencoded是基于它封装出来的
演示:(body-parser中间件的使用)
终端结果:
3.4 自定义中间件
说明
:
- 触发了req的data事件,就说明有数据被提交到服务器了。
- 触发了req的end事件,就说明数据已经发送完毕了,我们在服务器端已经完整地接收到了post提交到服务器的表单数据。
封装前的完整代码:
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 Node.js 内置的 querystring 模块
const newQs = require('querystring')
// 这是解析表单数据的中间件
app.use(function(req, res, next){
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
// 拼接请求体数据
str += chunk
})
// 3. 监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = newQs.parse(str)
// 挂载为自定义属性,供下游使用
req.body = body
next()
})
})
app.post('/user', (req, res) => {
// req.body能在Postman中成功反应,说明下游可以使用req.body,自定义属性挂载成功了
res.send(req.body)
})
// 启动服务器
app.listen(880, function() {
console.log('Express server running at http://127.0.0.1')
})
演示:
封装的独立模块
使用前面封装好的独立模块进行处理
结果:
4. 使用Express写接口
4.1 创建基本的服务器
4.2 创建 API 路由模块
演示:(
4.3 编写GET接口
开始,演示代码是在以下两个代码的基础上改进的)
4.3 编写 GET 接口
演示:
4.4 编写 POST 接口
演示:
结果:
4.5 CORS 跨域资源共享
演示:(由于我们有 GET 和 POST请求,得选 CORS 解决方案来处理接口的跨域问题)
从staticfile.org
中复制箭头所指地址到html文件中导入
(复制文件地址,从CDN中载入jQuery。)在html文件中使用
这个演示需要两个文件,一个是如下的js文件负责开启服务器和配置中间件,一个是html文件负责验证我们解决了接口的跨域问题。
这里因为我的80端口被占用了,用的是880端口,使用时特别注意一下。(刚学的时候被这个小问题卡了半天
)
默认情况下,网页可以跨域请求,但是无法拿到请求回来的数据。
4.5.5 CORS 响应头部 - Access-Control-Allow-Origin
4.5.6 CORS 响应头部 - Access-Control-Allow-Headers
4.5.7 CORS 响应头部 - Access-Control-Allow-Methods
演示:
用火狐浏览器打开html文件,点击GET按钮,发送一次请求
。而点击DELETE按钮,是发送两次请求
(第一次是预检请求,预检成功之后才会发第二次,第二次是真正的请求)。
4.6 JSONP 接口
演示:(JSONP
)
html文件中的改动:(设置JSONP按钮,同时为其绑定点击事件处理函数)
注意
:method: 'GET'
和 dataType: 'jsonp'
都是很重要的,同时满足才能发起真正的JSONP请求
网页html文件的完整代码如下:
<!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>Document</title>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<button id="btnDELETE">DELETE</button>
<button id="btnJSONP">JSONP</button>
<script>
$(function () {
// 1. 测试 GET 接口
$('#btnGET').on('click', function() {
$.ajax({
type: 'GET',
url: 'http://127.0.0.1:880/api/get',
data: { name: 'theshy', age: 22 },
success: function (res) {
console.log(res)
},
})
})
// 2. 测试 POST 接口
$('#btnPOST').on('click', function() {
$.ajax({
type: 'POST',
url: 'http://127.0.0.1:880/api/post',
data: { bookname: '水浒传', author: '施耐庵' },
success: function (res) {
console.log(res)
},
})
})
// 3. 为删除按钮绑定点击事件处理函数
$('#btnDELETE').on('click', function() {
$.ajax({
type: 'DELETE',
url: 'http://127.0.0.1:880/api/delete',
data: { name: 'theshy', age: 22 },
success: function (res) {
console.log(res)
},
})
})
// 4. 为 JSONP 按钮绑定点击事件处理函数
$('#btnJSONP').on('click', function(){
$.ajax({
method: 'GET',
url: 'http://127.0.0.1:880/api/jsonp',
dataType: 'jsonp', // 表示要发起 JSONP 请求
success: function (res) {
console.log(res)
}
})
})
})
</script>
</body>
</html>
服务器js文件中的改动:(配置 JSONP 的接口)
服务器js文件具体代码如下:
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
// 在jsonp前加/api是因为它没有在路由中进行统一注册,需要手动加一个/api
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback
// 2. 定义要发送到客户端的数据对象
const data = { name: 'theshy', age: 22}
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./15-apiRouter')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(880, function() {
console.log('Express server running at http://127.0.0.1')
})
检验结果:(在火狐浏览器运行html文件)
点击 JSONP 按钮,控制台中成功打印出数据。运行成功!说明我们写的 JSONP 接口确实可以正常工作。