# Express 简介
# 什么是 Express
官方给出的概念:Express 是 基于 Node.js 平台,快速、开放、极简的 Web 开发框架
通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似, 是专门用来创建 Web 服务器的 。
Express 的本质 :就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
Express 中文官网:https://www.expressjs.com.cn/
- http 和 Express 的关系类似于浏览器中 Web API 和 jQuery 的关系。后者是基于前者进一步封装出来的。
- 为什么有了 http 内置模块,还要用 Express?因为 http 内置模块用起来很复杂,开发效率低;Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率。
# Express 可以做什么
对于前端来说,最常见的 两种 服务器,分别是:
Web 网站服务器:专门对外提供 Web 网页资源的服务器API 接口服务器:专门对外提供 API 接口的服务器
使用 Express,我们可以方便、快速的创建 Web 网站 的服务器或 API 接口 的服务器。
# 安装
在项目所处的目录中,运行如下的终端命令,即可将 express 安装到项目中使用:
npm i express@4.17.1
# 基本使用
-
创建基本的 Web 服务器
// 导入 express const express = require("express"); // 创建 web 服务器 const app = express(); // 启动 web 服务器 app.listen(80, () => { console.log("express server running at:http:127.0.0.1"); }); -
监听 GET 请求
通过app.get()方法,可以监听客户端的 GET 请求,具体的语法如下:// 参数1:客户端请求的URL地址 // 参数2:请求对应的处理函数 // req:请求对象(包含了与请求相关的属性与方法) // res:响应对象(包含了与请求相关的属性与方法) app.get("请求URL", function (req, res) { /* 处理函数 */ }); -
监听 POST 请求
通过app.post()方法,可以监听客户端的 POST 请求,具体的语法如下:// 参数1:客户端请求的URL地址 // 参数2:请求对应的处理函数 // req:请求对象(包含了与请求相关的属性与方法) // res:响应对象(包含了与请求相关的属性与方法) app.post("请求URL", function (req, res) { /* 处理函数 */ }); -
把内容响应给客户端
通过res.send()方法,可以把处理好的内容,发送给客户端:app.get("/user", (req, res) => { // 向客户端发送 JSON 对象 res.send({ name: "zs", age: 20, gender: "男" }); }); app.post("/user", (req, res) => { // 向客户端发送温本内容 res.send("请求成功!"); }); -
获取 URL 中携带的查询参数
通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数app.get("/", (req, res) => { // req.query 默认是一个空对象 // 客户端使用 ?name=zs&age=20 这种查询字符串形式,发送到服务器的参数 // 可以通过 req.query对象访问到,例如: // req.query.name req.query.age console.log(req.query); }); -
获取 URL 中的动态参数
通过req.params对象,可以访问到 URL 中,通过:匹配到的动态参数
举例:/my/article/deletecate/:id,这里的:id 就是动态参数// 这里的 :id 是一个动态的参数 app.get("/user/:id/:name", (req, res) => { // req.params 默认是一个空对象 // 里面存放着通过 : 动态匹配到的参数值 console.log(req.params); res.send(req.params); }); -
整体案例
const express = require("express"); const app = express(); app.get("/user", (req, res) => { // 向客户端响应 JSON 对象 res.send({ name: "zs", age: 20, gender: "男" }); }); app.post("/user", (req, res) => { // 向客户端响应文本对象 res.send({ name: "giao", age: 20, gender: "男" }); }); app.get("/", (req, res) => { // req.query 默认是一个空对象 // 客户端使用 ?name=zs&age=20 这种查询字符串形式,发送到服务器的参数 // 可以通过 req.query对象访问到,例如: // req.query.name req.query.age console.log(req.query); res.send(req.query); }); // 这里的 :id 是一个动态的参数 app.get("/user/:id/:name", (req, res) => { // req.params 默认是一个空对象 // 里面存放着通过 : 动态匹配到的参数值 console.log(req.params); res.send(req.params); }); app.listen(80, () => { console.log("express server running at:http://127.0.0.1"); });
# 托管静态资源
-
express.static()
express 提供了一个非常好用的函数,叫做express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如:通过如下代码就可以将 public 目录下的图片,CSS 文件,Javascript 文件对外开放访问了。app.use(express.static("public"));现在,你就可以访问 public 目录中的所有文件了:
- http://localhost:3000/images/bg.jpg
- http://localhost:3000/css/style.css
- http://localhost:3000/js/login.js
注意:Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在 URL 中。 -
托管多个静态资源目录
如果要托管多个静态资源目录,请多次调用 express.static () 函数app.use(express.static("public")); app.use(express.static("files"));访问静态资源文件时,express.static () 函数会根据目录的添加顺序查找所需的文件。
-
挂载
路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以
使用如下的方式:app.use("/public", express.static("public"));现在,你就可以通过带有
/public前缀地址来访问 public 目录中的文件了: -
案例
const express = require("express"); const app = express(); // 在这里,调用 express.static() 方法,快速的对外提供静态资源 // 这里的代码必须在clock所在的根目录,编译后才有效果 // 这里的 clock 在该项目的根目录 // http://127.0.0.1/index.html 就可以访问对应的文件 app.use(express.static("./clock")); app.use("/files", express.static("./files")); // 这里必须得是http://127.0.0.1/files/index.html 才可以访问files文件夹下的index.html // 前缀一般和文件夹目录名字保持一致 app.listen(80, () => { console.log("express server running at:http://127.0.0.1"); });
# nodemon
在编写调试 Node.js 项目的时候,如果修改了项目的代码,则需要
频繁的手动 close 掉,然后再重新启动,非常繁琐。
现在,我们可以使用 nodemon 这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
npm install -g nodemon
以后启动 Node 项目就可以使用 nodemon 项目名 就可以了,而不是使用 node 项目名
# Express 路由
# 什么是路由
路由就是映射关系,比如 10086 中的按键和对应的办理业务的映射关系。
在 Express 中,路由指的是 客户端的请求 与 服务器处理函数 之间的 映射关系 。
Express 中的路由分 3 部分组成,分别是 请求的类型 、 请求的 URL 地址 、 处理函数 ,格式如下:
app.METHOD(path, HANDLER);
// METHOD:请求的类型 path:请求的URL地址 HANDLER:处理函数
# 路由匹配过程
每当一个请求到达服务器之后, 需要先经过路由的匹配 ,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行匹配,如果 请求类型 和 请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理。
路由匹配的注意点:
- 按照定义的
先后顺序进行匹配 请求类型和请求的 URL同时匹配成功,才会调用对应的处理函数。
# 路由的使用
# 最简单的用法
在 Express 中使用路由最简单的方式,就是把路由挂载到 app 上,示例代码如下:
const express = require("express");
const app = express();
app.get("/", (req, res) => {
// 向客户端响应 JSON 对象
res.send({ name: "zs", age: 20, gender: "男" });
});
app.post("/", (req, res) => {
// 向客户端响应文本对象
res.send({ name: "giao", age: 20, gender: "男" });
});
app.listen(80, () => {
console.log("express server running at:http://127.0.0.1");
});
# 模块化路由
为了 方便对路由进行模块化的管理 ,Express 不建议 将路由直接挂载到 app 上,而是 推荐将路由抽离为单独的模块 。
将路由抽离为单独模块的步骤如下:
- 创建路由模块对应的.js 文件
- 调用
express.Router()函数创建路由对象 - 向路由对象上挂载具体的路由
- 使用
module.exports向外共享路由对象 - 使用
app.use()函数注册路由模块
-
案例
-
router.js
// 这是路由模块 const express = require("express"); // 创建路由对象 const router = express.Router(); // 挂载具体的路由 router.get("/user/list", (req, res) => { console.log("Get user List."); res.send("Get user List."); }); router.post("/user/add", (req, res) => { console.log("Add new user."); res.send("Add new user."); }); // 向外导出路由对象 module.exports = router; -
index.js
const express = require("express"); const app = express(); // 导入路由模块 const router = require("./4router"); // 注册路由模块 // app.use(router); app.use("/api", router); // 为路由模块添加前缀,类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单,这里添加统一的访问前缀 /api // 如果没加前缀,请求地址是 http://127.0.0.1接口地址 // 加了前缀,请求地址是:http://127.0.0.1/api接口地址 // 注意: app.use() 函数的作用,就是来注册全局中间件的 app.listen(80, () => { console.log("server running at:http://127.0.0.1"); });
-
# Express 中间件
# 什么是中间件 (Middleware)
特指 业务流程 的 中间处理环节 ,比如在处理污水的时候,从污水到达到排放标准的废水,可能要经过一些环节,这些处理环节,就可以叫做中间件。
# Express 中间件的调用流程
当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行 预处理 。
中间件就是对请求进行预处理的。
# Express 中间件的格式
Express 的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下:
var express = require("express");
var app = express();
app.get("/", function (req, res, next) {
next();
});
// 这里的 function就是中间件,里边还有next函数
app.listen(3000);
注意:中间件函数的形参列表中, 必须包含 next 参数 。而路由处理函数中只包含 req 和 res。
# next 函数的作用
next 函数 是实现 多个中间件连续调用 的关键,它表示把流转关系 转交 给下一个 中间件 或 路由 。
# Express 全局中间件
# 全局生效的中间件
客户端发起的 任何请求 ,到达服务器之后, 都会触发的中间件 ,叫做全局生效的中间件。
通过调用 app.use (中间件函数),即可定义一个全局生效的中间件,示例如下:
const express = require("express");
const app = express();
// const mw = function (req, res, next) {
// console.log("这是要给最简单的中间件函数");
// // 把流转关系,转交给下一个中间件或路由
// next();
// };
// // 将 mw 注册为全局生效的中间件
// app.use(mw);
app.use((req, res, next) => {
console.log("这是要给最简单的中间件函数");
next();
});
app.get("/", (req, res) => {
console.log("调用了 / 这个路由");
});
app.get("/user", (req, res) => {
console.log("调用了 /user 这个路由");
});
// 请求 / 或 /user 的时候,会先去到中间件,然后才会被 next 流转到对应的接口
app.listen(80, () => {
console.log("server running at:http://127.0.0.1");
});
# 中间件的作用
多个中间件之间, 共享同一份 req 和 res 。基于这样的特性,我们可以在 上游 的中间件中, 统一 为 req 或 res 对象添加 自定义 的 属性 或 方法 ,供 下游 的中间件或路由进行使用。
const express = require("express");
const app = express();
app.use((req, res, next) => {
// 获取到请求到达服务器的时间
const time = Date.now();
// 为 req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time;
next();
});
app.get("/", (req, res) => {
res.send("Home page." + req.startTime);
});
app.get("/user", (req, res) => {
res.send("User page." + req.startTime);
});
app.listen(80, () => {
console.log("server running at:http://127.0.0.1");
});
# 定义多个全局中间件
可以使用 app.use () 连续定义多个 全局中间件。客户端请求到达服务器之后,会按照中间件 定义的先后顺序 依次进行调用,示例代码如下:
// 第一个全局中间件
app.use((req, res, next) => {
console.log("调用了第1个全局中间件");
next();
});
// 第二个全局中间件
app.use((req, res, next) => {
console.log("调用了第2个全局中间件");
next();
});
// 请求这个路由,会依次触发上述两个全局中间件
app.get("/user", (req, res) => {
res.send("Home page.");
});
# 局部生效的中间件
不使用 app.use () 定义的中间件,叫做 局部生效的中间件 ,示例代码如下:
const express = require("express");
const app = express();
// 定义中间件函数 mw1
const mw1 = function (req, res, next) {
console.log("这是中间件函数");
next();
};
// mw1 这个中间件只在'当前路由中生效',这种用法属于'局部生效的中间件'
app.get("/", mw1, function (req, res) {
console.log("Home page.");
res.send("Home page.");
});
// mw1 这个中间件不会影响下面这个路由 ↓↓↓
app.get("/user", function (req, res) {
console.log("User page.");
res.send("User page.");
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
# 定义多个局部中间件
可以在路由中,通过如下两种 等价 的方式, 使用多个局部中间件 :
// 请求也会按照顺序执行,会先执行 mw1,然后执行mw2……,最后把请求交给路由处理函数进行最终的处理
app.get("/", mw1, mw2, (req, res) => {
res.send("Home page.");
});
app.get("/", [mw1, mw2], (req, res) => {
res.send("Home page.");
});
const express = require("express");
const app = express();
// 定义中间件函数 mw1
const mw1 = function (req, res, next) {
console.log("这是第一个中间件函数");
next();
};
const mw2 = function (req, res, next) {
console.log("这是第二个中间件函数");
next();
};
// mw1 这个中间件只在'当前路由中生效',这种用法属于'局部生效的中间件'
app.get("/", mw1, mw2, function (req, res) {
console.log("Home page.");
res.send("Home page.");
});
// mw1 这个中间件不会影响下面这个路由 ↓↓↓
app.get("/user", function (req, res) {
console.log("User page.");
res.send("User page.");
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
# 中间件的五个使用注意事项
- 一定要在
路由之前注册中间件 - 客户端发送过来的请求,
可以连续调用多个中间件进行处理 - 执行完中间件的业务代码之后,
不要忘记调用next()函数 为了防止代码逻辑混乱,调用 next () 函数后不要再写额外的代码- 连续调用多个中间件时,多个中间件之间,
共享req 和 res 对象
# 中间件的分类
Express 官方把 常见的中间件用法 ,分成了 5 大类 ,分别是:
应用级别的中间件路由级别的中间件错误级别的中间件Express 内置的中间件第三方的中间件
# 应用级别的中间件
通过 app.use() 或 app.get() 或 app.post() , 绑定到 app 实例上的中间件 ,叫做应用级别的中间件,代码示例如下:
// 应用级别的中间件(全局中间件)
app.use((req, res, next) => {
next();
});
// 应用级别的中间件(局部中间件)
app.get("/", mw1, (req, res) => {
res.send("Home page.");
});
# 路由级别的中间件
绑定到 express.Router () 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上,代码示例如下:
const express = require("express");
var app = express();
const router = express.Router();
// 路由级别的中间件
router.use(function (req, res, next) {
next();
});
app.use("/", router);
# 错误级别的中间件
错误级别中间件的 作用 :专门用来捕获整个项目中发生的异常错误,从而防止项目异常奔溃的问题。
格式 :错误级别中间件的 function 处理函数中, 必须有 4 个形参 ,形参顺序从前到后,分别是 ( err ,req,res,next)
const express = require("express");
const app = express();
app.get("/", function (req, res) {
// 人为制造错误
throw new Error("服务器内部发生了错误!");
// 只要发生了错误,就立即走到错误中间件去执行,下面的res.send()没有执行
res.send("User page.");
});
// 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的奔溃
app.use(function (err, req, res, next) {
console.log("发生了错误!" + err.message);
res.send("Error:" + err.message);
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
注意 :错误级别的中间件, 必须注册在所有路由之后 !
# Express 内置的中间件
由 Express4.16.0 版本开始,Express 内置了 3 个 常用的中间件,极大的提高了 Express 项目的开发效率和体验:
-
express.static快速托管静态资源的内置中间件,例如:HTML 文件、图片、CSS 样式等 (无兼容性) -
express.json解析 JSON 格式的请求体数据 (有兼容性,仅在 4.16.0+ 版本中可用)const express = require("express"); const app = express(); // 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置 // 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据 // 配置解析 application/json 格式数据的内置中间件 app.use(express.json()); app.post("/user", function (req, res) { // 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据 // 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined console.log("req-->", req.body); res.send("User page."); }); app.listen(80, () => { console.log("http://127.0.0.1"); }); -
express.urlencoded解析 URL-encoded 格式的请求体数据 (有兼容性,仅在 4.16.0 + 版本中可用)const express = require("express"); const app = express(); // 配置解析 application/x-www-form-urlencoded 格式数据的内置中间件 // extended: false 这个记得要写 app.use(express.urlencoded({ extended: false })); app.post("/book", function (req, res) { // 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据 console.log("req-->", req.body); res.send("Book page."); }); app.listen(80, () => { console.log("http://127.0.0.1"); });
# 第三方的中间件
非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,可以 按需下载 并 配置 第三方中间件,从而提高项目的开发效率。
例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:
- 运行
npm install body-parser安装中间件 - 使用
require导入中间件 - 调用
app.use()注册并使用中间件
注意 :Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的。
# 自定义中间件
手动模拟 一个 来解析 POST 提交到服务器的表单数据 的中间件
-
案例
-
自定义中间件.js
const expres = require("express"); const app = expres(); const customBodyParser = require("./13.自定义中间件封装为模块"); // 这里是解析表单数据的中间件 app.use(customBodyParser); app.post("/user", (req, res) => { // 这里是上下游的中间件共享同一个 req,所以这里可以成功输出 req.body res.send(req.body); }); app.listen(80, () => { console.log("server running at http://127.0.0.1"); }); -
自定义中间件封装之后的模块.js
这里是将代码单独剥离出来的// node 内置的 querystring 模块,专门用来处理查询字符串 const qs = require("querystring"); const bodyParser = (req, res, next) => { // 用来存储客户端发送过来的请求体数据 let str = ""; // 在中间件中,需要监听 req 对象的 data事件,来获取客户端发送到服务器的数据 // 如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。 req.on("data", (chunk) => { str += chunk; }); // 当请求体数据接收完毕之后,会自动触发 req 的 end 事件 // 因此,我们可以在 req 的end事件中,拿到并处理完整的请求体数据 req.on("end", () => { // 把字符串格式的请求体数据,解析成对象格式 // 通过 querystring 模块的 parse 方法可以把查询字符串解析成对象的格式 const body = qs.parse(str); // 注意:上游的中间件和下游的中间件以及路由之间,共享同一份 req 和 res。因此,我们可以把解析出来的数据,挂在为 req 的自定义属性,命名为 req.body,供下游使用。 req.body = body; next(); }); }; module.exports = bodyParser;
-
# 使用 Express 写接口
-
如果要获取 URL-encoded 格式的请求体数据,必须配置中间件
-
什么是 CORS
CORS (跨域资源共享) 由一系列HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页 "跨域" 获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可以解除浏览器端的跨域访问限制。-
CORS 注意事项
- CORS 主要在
服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口 - CORS 在浏览器中
有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口 (例如:IE10+,Chrome4+,FireFox3.5+)
- CORS 主要在
-
CORS 相关的三个响应头
-
Access-Control-Allow-Origin:
| *
其中,origin 参数的值指定了允许访问该资源的外域 URL。例如,下面的字段值只允许来自 http://giaogiao.cn 的请求。* 表示允许来自任何域的请求res.setHeader("Access-Control-Allow-Origin", "http://giaogiao.cn"); -
Access-Control-Allow-Headers
默认情况下,CORS 仅支持客户端向服务器发送如下的个请求头:Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)如果客户端向服务器
发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败!// 允许客户端额外向服务器发送 Content-Type 请求头和 X-Custom-Header 请求头 // 注意:多个请求头之间使用英文的逗号进行分割 res.setHeader( "Access-Control-Allow-Headers", "Content-Type, X-Custom-Header" ); -
Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,Access-Control-Allow-Methods 来指明实际请求所允许使用的 HTTP 方法// 只允许 POST、GET、DELETE、HEAD请求方式 res.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, HEAD"); // 允许所有的 HTTP 请求方式 res.setHeader("Access-Control-Allow-Methods", "*");
-
-
CORS 请求的分类
客户端在请求 CORS 接口时,根据请求方式和请求头的不同,可以将 CORS 的请求分为两大类:分别是简单请求和预检请求-
简单请求
同时满足以下两大条件的请求,就属于简单请求:请求方式:GET、POST、HEAD 三者之一HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
-
预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求- 请求方式为
GET、POST、HEAD 之外的请求 Method 类型 - 请求头中包含
自定义头部字段 - 向服务器发送了
application/json 格式的数据
在浏览器与服务器正是通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为 "预检请求"。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
- 请求方式为
-
简单请求和预检请求的区别
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
-
-
-
案例
-
调用文件.js
const express = require("express"); const app = express(); // npm i cors 安装跨域中间件 // 一定要在路由之前,配置 cors 中间件,从而解决接口跨域的问题 const cors = require("cors"); app.use(cors()); // 这样就可以跨域请求了 // 注意:如果要获取 URL-encoded 格式的请求体数据,必须配置中间件 app.use(express.urlencoded({ extended: false })); // 导入路由模块 const router = require("./15.apiRouter"); // 把路由模块,注册到 app 上 // 之后请求接口,要加 /api 前缀 app.use("/api", router); // http://127.0.0.1/api/get?qqq=123&ttt=fsdfs 会执行模块的 get 请求 // http://127.0.0.1/api/post app.listen(80, () => { console.log("server running at http://127.0.0.1"); }); -
路由模块.js
const expres = require("express"); const router = expres.Router(); router.get("/get", (req, res) => { // 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据 const query = req.query; res.send({ status: 0, msg: "get请求成功", data: query, }); }); router.post("/post", (req, res) => { // 通过 req.body 获取客户端通过请求体,发送到服务器的 URL-encoded 数据 const body = req.body; res.send({ status: 0, msg: "post请求成功", data: body, }); }); // 注意:如果要获取 URL-encoded 格式的请求体数据,必须配置中间件 app.use(express.urlencoded({extended:false})) module.exports = router;
-
# 数据库
# 数据库的基本概念
# 什么是数据库
数据库 (database) 是用来 组织、存储和管理 数据的仓库。用户可以对数据库中的数据进行 新增、查询、更新、删除 等操作。
# 常见的数据库
- MySQl 数据库 (目前使用最广泛、流行度最高的开源免费数据库;Community+Enterprise)
- Oracle 数据库 (收费)
- SQL Server 数据库 (收费)
- Mongodb 数据库 (Community+Enterprise)
- Redis 数据库 (免费)
前三个属于 传统型数据库 (又叫做 关系型数据库 或 SQL 数据库 ),Mongodb 和 Redis 属于 新型数据库 ( 非关系型数据库 或 NoSQL(Not Only SQL) 数据库 ),它在一定程度上弥补了传统型数据库的缺陷。
# 传统型数据库的数据组织结构
数据的组织结构:指的就是数据以什么样的结构进行存储。
传统型数据库的数据组织结构,与 Excel 中的数据组织结构比较类似 (数据库 (database) 工作簿、数据表 (table) 工作表、数据行 (row) 行、字段 (field) 列)。
- 实际开发中库、表、行、字段的关系
- 在实际项目开发中,一般情况下,每个项目都对应
独立的数据库 - 不同的数据,要存储到数据库的不同表中,例如:用户数据存储到 users 表中,图书数据存储到 books 表中
- 每个表中具体存储哪些信息,由字段来决定,例如:我们可以为 users 表设计 id、username、password 这三个字段。
- 表中的行,代表每一条具体的数据。
- 在实际项目开发中,一般情况下,每个项目都对应
# 安装并配置 MqSQl
- MqSQl Server: 专门用来提供数据存储和服务的软件
- MqSQl Workbench: 可视化的 MqSQl 管理工具,通过它,可以方便的操作存储在 MqSQl Server 中的数据。
账号:root 密码:admin123 端口:3307
如果卡在了 Starting the server 这一步,打卡服务 - 找到 MySQL80 - 右击打开属性,找到登录选项卡,切换为本地系统账户即可。
具体安装教程去对应文件夹中查看
# MqSQl 基本使用
# 使用 MqSQl Workbench 管理数据库
-
连接数据库
在最左侧第一个选项卡中,选择主界面对应的选项,然后弹出输入密码框,密码正确则连接数据库并跳转到 -
主界面的组成部分
菜单栏,工具栏,数据库列表 (navigator 中的 Schemas),信息区域 (information),代码编辑区 (query),输出区 (Output) -
创建数据库
工具栏第四个按钮创建数据库,库名要是英文,之后点击 Apply,之后会在左侧的数据库区域出现新建的数据库,然后双击进入 -
创建数据表
找到对应的数据库,在 Tables 上右键选择 create Table,Table Name 是表名,Comments 是表的描述- 字段的特殊标识:
- PK (Primary Key):主键,唯一标识
- NN (Not Null):值不允许为空
- UQ (Unique):值唯一
- AI (Auto Increment):值自动增长
- 字段的特殊标识:
-
向表中写入数据
找到对应的表,右键选择 Select Rows-Limit1000,写完数据之后要记得点击 Apply
# 使用 SQl 管理数据库
-
什么是 SQL
SQL (Structured Query Language) 是结构化查询语言,专门用来访问和处理数据库的编程语言。能够让我们以编程的形式,操作数据库里面的数据。SQL 语言
只能在关系型数据库中使用 -
SQL 能做什么
可以对数据库中的数据库进行增删改查操作,也可以创建新数据库,在数据库中创建新表,可以在数据库中创建存储过程,视图等
# SQl 的 增删改查语句
-
SELECT
Select 语句用于从表中查询数据。执行的结果被存储在一个结果表中 (成为结果集)。语法格式如下:
// 从 FROM 指定的表中,查询出所有的数据。* 表示所有列 SELECT * FROM 表名称 // select * from users // 从 FROM 指定的表中,查询出指定列名称(字段)的数据。 SELECT 列名称 FROM 表名称 // select username,password from users注意:SQL 语句中的关键字对大小写不敏感。SELECT 等同于 selsect,FROM 等同于 from
-
INSERT INTO
INSERT INTO 语句用于
向数据表中插入新的数据行,语法格式如下:// 语法解读:向指定的表中,插入如下几列数据,列的值通过 value 一一指定 // 注意:列和值要一一对应,多个列和多个值之间,使用英文的逗号分隔 INSERT INTO table_name (列1,列2……) VALUES (值1,值2……) // insert into users (username,password) values ('tony stark','098123') -
UPDATE
UPDATE 语句用于
修改表中的数据,语法格式如下:// 语法解读: // 1. 用 update 指定要更新哪个表中的数据 // 2. 用 set 指定列对应的新值 // 3. 用 where 指定更新的条件 UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称= 某值 // update users set password = '888888' where id = 4 // update users set password = 'admin123', status=1 where id = 2 -
DELETE
DELETE 语句用于
删除表中的数据行,语法格式如下:// 语法解读:从指定的表中,根据 where 条件,删除对应的数据行 DELETE FROM 表名称 WHERE 列名称 = 值 // delete from users where id = 4
# SQl 的 WHERE 子句
-
语法
WHERE 子句用于限定选择的标准。在 SELECT、UPDATE、DELTE 语句中,皆可使用 WHERE 子句来限定选择的标准。 -
可在 WHERE 子句中使用的运算符
=,<>(不等于),>,<,>=,<=,BETWEEN (在某个范围内),LIKE (搜索某种模式)
注意:在某些版本的 SQL 中,操作符 <> 可以写为!=select * from users where username <> 'ls' select * from users where username != 'ls'
# SQl 的 AND 和 OR 运算符
-
语法
AND 和 OR 可在 WHERE 子句中
把两个或多个条件结合起来AND 表示
必须同时满足多个条件,相当于 Js 中的 && 运算符,例如 if (a!==10 && a!==20)
OR 表示只要满足任意一个条件即可,相当于 Js 中的 || 运算符,例如 if (a!==10 || a!==20) -
示例
// 显示所有状态为0且id小于3的用户 select * from users where status = 0 and id < 3 // 显示所有 status 为 1,或者 username 为 zs 的用户 select * from users where status = 1 || username = 'zs'
# SQl 的 ORDER BY 子句
-
语法
ORDER BY 语句用于根据指定的列对结果集进行排序。ORDER BY 语句默认按照升序对记录进行排序。
如果希望按照降序对记录进行排序,可以使用DESC关键字。 -
示例
// 这两个 sql 是等价的,对 users 表中的数据,按照 status 字段进行升序排序 select * from users order by status; select * from users order by status asc; // 对 users 表中的数据,按照 id 字段进行降序排序 // desc 表示降序排序 asc表示升序排序(默认就是升序) select * from users order by id desc; // 对 usres 表中的数据,先按照 status 字段进行降序排序,再按照 username 的字母顺序,进行升序排序 select * from users order by status desc, username asc(这个asc可写可不写)
# SQl 的 count (*) 函数
-
语法
count (*) 函数用于返回查询结果的总数据条数,语法格式如下:select count(*) from 表名称 -
使用 as 为列设置别名
如果希望给查询出来的列名称设置别名,可以使用
as关键字select username as name, password as pwd from users -
示例
// 查询 usres 表中 status 为 0 的总数居条数 select count(*) as total from users where status = 0 ;
# 在项目中操作 MySQL
# 在项目中操作 MySQL 的步骤
-
安装操作 MySQL 数据库的第三方模块 (
mysql)npm install mysql -
通过 mysql 模块
连接到 MySQL 数据库 -
通过 mysql 模块
执行 SQL 语句 -
案例
// 1. 导入 mysql 模块 const mysql = require("mysql"); // 2. 建立与 MySQL 数据库的连接关系 const db = mysql.createPool({ host: "127.0.0.1", // 数据库的 IP port: "3307", // 端口号 user: "root", // 登录数据库的账号 password: "admin123", // 登录数据库的密码 database: "my_db_01", // 指定要操作哪个数据库 }); // 3. 检测 mysql 模块能否正常工作 // 调用 db.query() 函数,指定要执行的 SQL 语句,通过回调函数拿到执行的结果 // select 1 没有任何实际的作用,只是用来检测 mysql 模块是否能够正常工作 db.query("select 1", (err, results) => { if (err) return console.log(err.message); console.log(results); // [ RowDataPacket { '1': 1 } ] });
# 使用 mysql 模块操作 MySQL 数据库
// 1. 导入 mysql 模块
const mysql = require("mysql");
// 2. 建立与 MySQL 数据库的连接关系
const db = mysql.createPool({
host: "127.0.0.1", // 数据库的 IP
port: "3307", // 端口号
user: "root", // 登录数据库的账号
password: "admin123", // 登录数据库的密码
database: "my_db_01", // 指定要操作哪个数据库
});
const queryStr = "select * from users";
// 查询数据
db.query(queryStr, (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 select 查询语句,则执行的结果是大数组,数组的每一项由对象组成
console.log(results);
});
// 插入数据
// 要插入到 users 表的数据
const user = { username: "Spider-Man", password: "pcc321" };
// 待执行的 SQL 语句,其中英文的 ? 表示占位符
const insertStr = "insert into users (username,password) values (?,?)";
// 使用数组的形式,依次为 ? 占位符指定具体的值
db.query(insertStr, [user.username, user.password], (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象,可以通过 affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
console.log("插入数据成功");
}
});
// 插入数据的便捷方式:如果数据对象的每个属性和数据表的字段一一对应,则通过如下方式快速插入数据
// users 表中的字段有 id,username,password,status,其中id 是自增的,status有默认值
const user = { username: "Spider-Man2", password: "pcc4321" };
const insertStr = "insert into users set ?";
db.query(insertStr, user, (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象,可以通过 affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
console.log("插入数据成功");
}
});
// 更新数据
// 要更新的数据
const user = { id: 6, username: "aaa", password: "000" };
// 待执行的 SQL 语句,其中英文的 ? 表示占位符
const updateStr = "update users set username = ?, password = ? where id = ?";
// 使用数组的形式,依次为 ? 占位符指定具体的值
db.query(updateStr, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 update 语句,则 results 是一个对象,可以通过 affectedRows 属性,来判断是否更新数据成功
if (results.affectedRows === 1) {
console.log("更新数据成功");
}
});
// 更新数据的便捷方式
// 如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速更新表数据
const user = { id: 6, username: "aaaa", password: "0000" };
// 待执行的 SQL 语句,其中英文的 ? 表示占位符
const updateStr = "update users set ? where id = ?";
// 使用数组的形式,依次为 ? 占位符指定具体的值
db.query(updateStr, [user, user.id], (err, results) => {
if (err) return console.log(err.message);
// 注意:如果执行的是 update 语句,则 results 是一个对象,可以通过 affectedRows 属性,来判断是否更新数据成功
if (results.affectedRows === 1) {
console.log("更新数据成功");
}
});
// 删除数据
// 待执行的 SQL 语句,其中英文的 ? 表示占位符
// const deleteStr = "delete from users where id = ?";
// // 注意:如果 SQL 语句中有多个占位符,则必须使用数组为每个占位符指定具体的值,如果 SQL 语句中只有一个占位符,则可以省略数组
// db.query(deleteStr, 5, (err, results) => {
// if (err) return console.log(err.message);
// // 注意:如果执行的是 delete 语句,则 results 是一个对象,可以通过 affectedRows 属性,来判断是否删除数据成功
// if (results.affectedRows === 1) {
// console.log("删除数据成功");
// }
// });
// 标记删除
// 使用 delete 语句,会把真正的数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。
// 所谓的标记删除,就是在表中设置类似于 status 这样的状态字段,来标记当前这条数据是否被删除
// 当用户执行了删除动作的时候,我们并没有执行 delete 语句把数据删除掉,而是执行了 update 语句,将这条数据对应的 status 字段标记为删除即可。
const deleteStr = "update users set status = ? where id = ?";
db.query(deleteStr, [1, 6], (err, results) => {
if (err) return console.log(err.message);
if (results.affectedRows === 1) {
console.log("标记删除成功");
}
});