1. 简介
1.1 概念
Express是一个简介灵活的node.js web应用框架,提供了一系列强大特性 帮助创建各种web应用,和丰富的工具。使用Express可以快速搭建一个完整功能的网站
1.2 本质
对之前的内置模块http进行了封装,使用更方便
1.3 核心
可以设置中间件来响应HTTP请求
定义了路由表用于执行不同的HTTP请求动作
1.4* 核心概念
路由:根据不同url路径,调用后台不同的处理函数
中间件:从请求开始到响应结束这个业务流程中的所有处理环节
2. 安装使用
2.1 express安装
新建英文目录(目录名不以express开头)
进入目录 初始化(地址栏输入打开cmd:npm init -y)
在此处安装express(npm i express)
2.2 使用
新建js文件,编写代码
执行代码: node .\demo.js
//1. 导入express包
const express = require("express");
const path = require("path");
//2. 实例化对象
const app = express()
//3. 处理请求
app.get("/", function(req,res) {
//end原生。发字符串,send什么都能发
//res.sendFile(path.join(__dirname, "../file/news.html"))
res.send("<h1>Hello</h1>");
})
app.get("*", function(req,res) {
res.send("其它路径!")
})
//4. 设置端口
app.listen(3000, () => {console.log("访问地址:http://localhost:3000");})
2.3 express集成nodemon
1. 修改package.json的script脚本
"dev": "nodemon src/index.js"
2. 使用 npm run dev
执行代码
2.4 区别(对比http创建web服务)
不需要处理乱码问题
不需要判断请求方式
2.5 res.send()与res.end()
end:原生http模块方法 express也支持。只能传字符串,不能处理乱码问题
send:express中封装的方法。可以传任何类型,可以处理乱码问题
3. Express路由
3.1 概念
根据不同url路径,调用后台不同的处理函数
3.2 语法
app.请求方式(访问路径, 处理函数)
请求方式:get,post,put,delete
访问路径:也可以正则( * 通配符)
处理函数:匿名函数,匹配未列举的路径
3.3 注意事项
匹配顺序:从上到下 ↓
请求方式和访问路径同时匹配成功,express会将请求转移到对应的处理函数处理
4. 跨域请求问题
从一个请求跳到另外一个请求,协议/域名/端口 只要有一个不同,即为跨域请求
解决: res.setHeader('Access-Control-Allow-Origin','*') //允许跨域请求
5. Express增删改查
5.1 查询 app.get()
查询所有 http://localhost:3000/users 后台: /users 直接返回数组
查询一个 http://localhost:3000/users?id=1 后台: /users 通过req.query获取参数
查询一个 http://localhost:3000/users/1 后台 /users/:id 通过req.params获取参数
5.2 新增 app.post()
前台
http://localhost:3000/users application/json {"name":"赵六","age":26}
后台
获取请求体数据
设置新增用户的id
把新增的用户添加到数组
5.3 修改 app.put()
前台
http://localhost:3000/users/1 application/json {"name":"赵六","age":26}
后台
获取要修改元素的id
获取请求体数据
查找id相同的元素进行修改
5.4 删除 app.delete()
前台
后台
获取要修改元素的id
查找id相同的元素进行删除
🟢 代码
//1. 引入模块
const express = require("express");
//2. 创建server服务
const app = express();
const db = [
{id:1, name:"张三", age:23},
{id:2, name:"李四", age:24},
]
//3. 处理请求
//查全部
app.get("/users", function(req, res) {
//解决跨域请求
res.setHeader("Access-Control-Allow-Origin", "*")
res.send(db);
})
//查一个query
app.get("/users", function(req, res) {
//解决跨域请求
res.setHeader("Access-Control-Allow-Origin", "*")
//获取id
db.forEach(item => {
if (item.id == req.query.id){
res.send(item);
}
});
})
//查一个params
app.get("/users/:id", function(req, res) {
//解决跨域请求
res.setHeader("Access-Control-Allow-Origin", "*")
//获取id
db.forEach(item => {
if(item.id == req.params.id){
res.send(item);
}
})
})
//增
app.post("/users", function(req, res) {
//解决跨域请求
res.setHeader("Access-Control-Allow-Origin", "*")
let postData = "";
req.on("data", data => postData += data)
req.on("end", () => {
let item = JSON.parse(postData);
item.id = db.length + 1;
db.push(item);
res.send(db)
})
})
//删
app.delete("/users/:id", function(req,res) {
//解决跨域请求
res.setHeader("Access-Control-Allow-Origin", "*")
db.forEach((item, index) => {
if (item.id == req.params.id) {
db.splice(index, 1)
}
})
res.send(db)
})
//改
app.put("/users/:id", function(req, res) {
//解决跨域请求
res.setHeader("Access-Control-Allow-Origin", "*")
let getData = "";
req.on("data", data => getData += data)
req.on("end", () => {
db.forEach(item => {
let user = JSON.parse(getData); //??
if (item.id == req.params.id) {
item.name = user.name;
item.age = user.age;
}
})
res.send(db)
})
})
//4. 设置端口
app.listen(3000, () => { console.log(" http://localhost:3000"); })
6. 中间件
6.1 抽取及中间件简化
中间件函数
概念(middieWare)
指业务流程的中间处理环节,本质是一个function处理函数
调用流程
当一个请求到达express服务器之后,可以连续调用多个中间件,对请求进行预处理
分类
按作用范围
局部生效中间件
全局生效中间件
按级别分
express内置中间件
第三方 如:处理跨域请求
应用级中间件
路由级中间件
注意
中间件函数形参列表中必须含next参数,与路由处理函数不同(路由只包含req、res)
next作用:实现多个中间件连续调用,把流转关系移交到下一个中间件 或 处理函数。(接力棒)
6.2 中间件的范围划分
局部生效中间件
概念:只在某些访问路径中使用
举例:getPostData中间件,只在新增与修改 的路由中生效,处理请求时要将中间件函数名写在参数中
// 定义中间件函数m1
const m1 = function(req,res,next){
console.log('这是中间件m1');
next() // 以next结尾
}
app.get("/index", m1, m2, function(req, res) {}) //连用中间件
注意
要在路由前注册中间件
客户端发来的请求,可以连续调用多个中间件处理
执行完中间件业务代码后,务必调用next()!
防止代码逻辑混乱,以next()结尾
连续调用多个中间件,共享req和res对象
全局生效中间件
概念:客户端发起的任何请求,到达服务器都会触发的中间件
使用:使用app.use(中间件函数) 来定义全局中间件
6.3 🟡中间件的级别划分
内置中间件
概念:由express官方提供,常见3个
分类:
express.json 解析json格式的请求体数据
express.urlencoded 解析url-encode格式的请求体数据
express.static 快速托管静态资源,可以帮助加载html文件、css样式、图片
使用:
app.use(express.json())
app.use(express.urlencoded())
app.use(express.static(path.join(__dirname,"../../file/")))
引入内置中间件:注册托管静态资源的中间件 将在file文件夹下的静态进行托管
效果: http://localhost:3000/new.html 可以直接加载
index.html不必输入文件名 http://localhost:3000
第三方中间件
概念:第三方个人/团队开发,使用前需下载
分类:cors() 处理跨域请求
使用
下载:npm i cors
导包:const cors = require("cors")
引入:app.use(cors())
原理:每个响应中添加响应头
应用级中间件
概念:通过app.use()
app.get()
app.post()
绑定到app实例上的
注意:应用及中间件,可以全局可以局部,但都要绑定到app实例上
路由级中间件
概念:绑定到路由对象(express.router()
创建的)中间件
注意:路由级中间件用处不是很多,了解
6.4 🟢模块化路由
产生的原因
1. 路由很多时,若全都卸载app入口文件,文件太大不好维护
2. 项目中有不同类型的请求路径(用户相关,商品相关),都放在一个文件中不好维护
3. 为了便于对路由进行模块化管理,express不建议将路由直接挂在在app上(建议将路由抽成单独模块)
使用步骤
1. 创建路由模块对应的js文件,放在routes文件夹下
2. 调用express.router()创建路由器对象
3. 向路由对象挂载具体路由
4. 使用module.exports向外共享路由对象
5. 在入口文件中使用app.use() 注册路由模块
代码
调用 http://localhost:3000/goods/list
//导入express包
const express = require("express");
//实例化express对象
const app = express()
//导入路由模块
const goodsRouter = require("../routes/goods");
const usersRouter = require("../routes/users");
//注册各个路由模块
app.use("/goods", goodsRouter)
app.use("/users", usersRouter)
//设置监听端口
app.listen(3000, () => {
console.log("运行在: http://localhost:3000");
})
//导入express包
const express = require("express");
//使用express创建路由对象
const router = express.Router()
//向router挂在具体路由
router.get("/list", function(req, res) {
res.send("商品列表页")
})
router.get("/add", function(req, res) {
res.send("商品添加页")
})
//导出路由对象(对外共享
module.exports = router
7. 数据库操作
7.1 node连接数据库
报错解决:
alter user 'root'@'localhost' identified with mysql_native_password by '1234';
7.2 增删改查(数组实现,优化)
//使用内置中间件处理请求体
app.use(express.json());
app.use(express.urlencoded());
//使用第三方中间件处理跨域问题 --需下载安装
app.use(cors());
7.3 增删改查(数据库实现)
思路
1. 导包、创建连接、连接,三个步骤拷贝进去
2. 在路由里 编写sql语句,执行并返回数据
7.4 增删改查(提取数据库操作)
目的:将数据库操作的代码单独放到一个文件中,便于后期代码维护
做法
1. 将数据库导包、创建连接、连接 代码剪切进一个js文件中
2. 提供查询所有getAll 查询单个getById 增删改exec方法,方法里用promise封装
3. 导出这三个方法
4. 在后台代码中导入js文件,解构这三个方法并使用
模块导出简写:
//导出对象
module.exports = {
getAll: getAll,
getById: getById,
exec: exec,
};
//属性名和属性值相同,可简写为一个
module.exports = {
getAll,
getById,
exec,
};
//引入:const {getAll, exec}= require("./引入.js")
8. ES7 语法糖(async_await)
8.1 async 异步
作用:将同步函数变成异步函数(返回promise对象),就可以使用then() 调用
使用:
//定义
async function m1() {
return 222;
}
//调用
const p = m1()
p.then((data) => {
console.log(data); //222
});
8.2 await 等待
作用:await可以等待后面的 promise对象执行后,拿到返回的结果
注意:
1. await不能单独使用,必须跟async连用,存在于异步函数中
2. await后面必须跟一个promise对象,如果不是promise对象,自动转成promise对象
3. await表达式,返回的是promise执行后的结果
使用:
//在外面套一个异步函数壳,或将外层函数添加async关键字改为异步
async function fn() {
async function m1() {
return 222;
}
//await 等待m1返回的promise对象执行完后,拿到返回的值
const res = await m1();
console.log(res);
}
fn();
8.3 增删改查await改写
app.get("/users", async function(req, res) {
let sql = 'select * from student';
const data = await exec(sql);//执行SQL语句
res.send(data);//发给浏览器
})
9. 错误处理机制
throw "错误信息"
try{
//可能会出问题的代码
} catch (err) {
//出问题的处理方案
}
10. 🟢用户管理系统搭建
10.1 思路
1. 需求分析(系统框架、具体业务分析)
2. 技术方案
数据库设计
设计接口
前台:发送请求的url,需要的参数
后台:返回数据的格式
3. 技术实现(编写代码)
先写后端(express脚手架快速生成)再写前端
10.2 用户管理系统搭建
1. 全局安装脚手架(项目生成器) npm i express-generator -g
2. 创建项目文件夹,执行命令生成后端目录 express --no-view backEnd
在目录下创建一个`backEnd`的目录, 作为后端项目的目录
`--no-view`: 创建一个数据服务, 不提供页面服务
3. 在后端目录安装相关依赖(cd backEnd
,npm i
)
进入backEnd目录, 执行命令, 根据`package.json`中的依赖项, 安装项目所有的依赖
4. 启动项目
1)npm i nodemon -D
将nodemon作为开发时的依赖安装(会在package.json中, 生成devDependencies)
2)修改启动脚本: package.json>"scripts">"start": "nodemon ./bin/www"
3)安装mysql和cors包(npm i mysql
、npm i cors
)
安装好后会自动添加在package.json
4)执行 npm run start
可以在3000端口看到 express脚手架欢迎页面
5. 导入数据库操作模块
将写好的db文件夹拷贝到backEnd
在routes/users.js中导入模块
//1. 导入mysql包
const mysql = require("mysql");
//2. 创建连接,数据库参数可以放到配置文件中(config/index.js)
var con = mysql.createConnection({
host: "localhost",
port: 3306,
user: "root",
password: "1234",
database: "db01",
timezone: "SYSTEM",
});
//3. 连接数据库
con.connect();
//4. 查询所有(获取查询的数据)
function getAll(sql) {
return new Promise((resolve, reject) => {
//执行sql语句
con.query(sql, (err, data) => {
//有错,把失败的数据带回去
if (err) reject(err);
//将获取到的数据带回去
resolve(data);
});
});
}
//5. 查询一个(获取查询的数据)
function getById(sql) {
return new Promise((resolve, reject) => {
//执行sql语句
con.query(sql, (err, data) => {
//有错,把失败的数据带回去
if (err) reject(err);
//将获取到的数据带回去
//易报错!
resolve(data[0] ? data[0] : null);
});
});
}
//6. 管理(增删改)
function exec(sql) {
return new Promise((resolve, reject) => {
//执行sql语句
con.query(sql, (err, data) => {
//有错,把失败的数据带回去
if (err) reject(err);
//将获取到的数据带回去
resolve(data);
});
});
}
//导出方法
/* module.exports = {
getAll:getAll,
getById:getById,
exec:exec
} */
//属性名和属性值相同,可简写为一个
module.exports = {
getAll,
getById,
exec,
};
6. 导入跨域请求模块
在app.js中导入 const cors = require("cors");
在app.js中注册 app.use(cors())
注册路由之前
10.3 后端编写流程
1. 在用户【路由模块】users.js下编写代码
2. 编写获取所有用户,使用get请求,调用getAll()方法,返回所有用户的数组(data)
3. 编写获取一个用户,使用get请求,调用getById()方法,返回指定id的用户(data)
4. 编写新增一个用户,使用post请求,调用exec()方法,返回新增的用户(data.insertId)
5. 编写修改一个用户,使用put请求,调用exec()方法,返回新增的用户(无需data)
6. 编写删除一个用户,使用delete请求,调用exec()方法,设置状态码,返回空字符串(无需data)
es.status(204).send("")
var express = require('express');
//导入数据库操作模块
const {getAll, getById, exec} = require("../db");
var router = express.Router();
// 获取所有用户
router.get('/', async function(req, res, next) {
let sql = "select * from users";
res.send(await getAll(sql))
});
// 获取一个用户
router.get('/:id', async function(req, res, next) {
let sql = `select * from users where ${req.params.id} = id`;
res.send(await getById(sql))
});
// 添加一个用户
router.post('/', async function(req, res, next) {
let {name, age} = req.body;
let sql = `insert into users (name, age) values("${name}", ${age}) `;
let data = await exec(sql)
res.send({
id: data.insertId,
name,
age
})
});
// 修改一个用户
router.put('/:id', async function(req, res, next) {
let {name, age} = req.body;
let sql = `update users set name = "${name}", age = ${age} where id = ${req.params.id}`;
res.send({
id: req.params.id,
name,
age,
data: await exec(sql),
})
});
// 删除一个用户
router.delete('/:id', async function(req, res, next) {
let sql = `delete from users where id = ${req.params.id} `;
let data = await exec(sql)
//设置状态码
res.status(204).send("")
});
module.exports = router;
10.4 前端编写流程
查询(显示所有用户)
1. 新建 fontEnd 文件夹
2. 新建3个html页面
3. 编写list.html页面的骨架
4. 编写基本样式base.css
5. 编写list页面样式style.css
6. 引入jquery,发送ajax请求(已经引入了cors处理跨域请求)
7. 遍历获取到的数据,动态拼接 tr 元素,追加到table下面
<body>
<h1>用户列表</h1>
<div class="container">
<a href="./add.html" class="add-btn">添加</a>
<table class="user-list">
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>操作</th>
</tr>
</table>
</div>
<script>
$.ajax({
url: "http://localhost:3000/users",
type: "GET",
success: function (data) {
console.log(data); //js数组
//遍历数组
data.forEach((item) => {
// console.log(item);
const tr = `<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.age}</td>
<td>
<a href="./edit.html?id=${item.id}" class="edit-btn">修改</a>
<a href="#" onclick="delUser(${item.id})">删除</a>
</td>
</tr>`;
$(".user-list").append(tr);
});
},
});
//删除
function delUser(id) {
console.log(id);
$.ajax({
url: "http://localhost:3000/users/" + id,
type: "DELETE",
success: function (res) {
console.log(res);
alert("删除成功");
location.reload();
},
});
}
</script>
</body>
新增
1. 编写add.html页面骨架
2. 在style.css中,添加add.html的样式
3. 监听add按钮的点击,在点击事件中获取用户输入的姓名与年龄进行判断
4. 输入为空,直接返回。否则用ajax发送post请求,提交到后台,修改数据库中数据
5. 修改成功,弹出提示,跳转回list.html页
<body>
<h1>添加用户</h1>
<div class="container">
<ul>
<li>
<label for="userName">用户名</label>
<input type="text" name="userName" id="userName" placeholder="请输入用户名" />
</li>
<li>
<label for="userAge">年龄</label>
<input type="text" name="userAge" id="userAge" placeholder="请输入年龄" />
</li>
</ul>
<button class="submit-btn">提交</button>
</form>
<script>
$(".submit-btn").click(function () {
const name = $("#userName").val().trim()
const age = $("#userAge").val().trim()
//判断
if (name == "" || age == "") {
alert("用户名或年龄不能为空")
return
}
//发送异步请求
$.ajax({
url: "http://localhost:3000/users",
type: "POST",
data: { name, age },
success: function (res) {
alert("添加成功")
location.href = "./list.html";
},
});
});
</script>
</body>
修改
1. 编写edit.html骨架
2. 在list.html中为 修改a标签 的href拼接id属性
`href="./edit.html?id=${item.id}"`
动态绑定
3. 在edit.html页获取路径携带的id值
window.location.search.replace('?','').split('=')[1]
4. 用ajax发送get请求,把根据id查找到的值回显到表单中
$('#age').val(res.age)
5. 为按钮添加点击事件,获取用户输入的名字年龄,为空直接返回,否则用ajax发送put请求,提交到后台 修改数据库中数据
6. 修改成功,弹出提示,跳转到list.html页面
<body>
<h1>修改用户</h1>
<div class="container">
<ul>
<li>
<label for="userName">用户名</label>
<input type="text" name="userName" id="userName" placeholder="请输入用户名" />
</li>
<li>
<label for="userAge">年龄</label>
<input type="text" name="userAge" id="userAge" placeholder="请输入年龄" />
</li>
</ul>
<button class="submit-btn">修改</button>
</form>
<script>
const id = location.search.replace("?", "").split("=")[1]
$.ajax({
url: "http://localhost:3000/users/" + id,
type: "GET",
success: function (res) {
console.log(res);
//回显数据(设置input里的值)
// alert("查找成功")
$("#userName").val(res.name)
$("#userAge").val(res.age)
},
});
$(".submit-btn").click(function () {
const name = $("#userName").val().trim()
const age = $("#userAge").val().trim()
//判断
if (name == "" || age == "") {
alert("用户名或年龄不能为空")
return
}
//发送异步请求
$.ajax({
url: "http://localhost:3000/users/" + id,
type: "PUT",
data: { name, age },
success: function (res) {
console.log(res);
//回显数据(设置input里的值)
alert("修改成功")
location.href = "./list.html";
},
});
});
</script>
</body>
删除
1. 在list.html页 为删除a标签 添加点击事件,传入id
2. 弹出确认框
3. 用ajax发送delete请求,删除数据库中数据
4. 删除成功,刷新