express与中间件
中文官网:https://www.expressjs.com.cn/
nodemon工具
nodemon这个工具,能够监听项目文件的变动。
当代码被修改后,nodemon会帮我们自动重启项目,极大的方便了开发和调试
##安装
npm i -g nodemon
##使用
之前启动文件是 node xxx.js
现在我们使用 nodemon xxx.js 这样xxx.js修改后就会被nodemon监听到,然后实现自动重启项目的效果。
一、什么是Express
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
通俗的理解:Express 的作用和 Node.is 内置的 http 模块类似,是专门用来创建 Web 服务器的。
本质就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法,
http 内置模块用起来很复杂,开发效率低;
Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率。
二、安装
##在项目所属的目录中,执行如下命令:
npm i express@版本 如:npm i [email protected]
三、快速使用
1、创建基本的web服务器
//导入express
const express = require("express");
//创建web服务器
const app = express();
//设置监听端口
app.listen(80,()=>{
console.log("express server running at http://localhost:80 ");
});
2、监听GET请求
//req : 请求对象(包含了与请求相关的属性和方法)
//res : 响应对象(包含了与响应相关的属性和方法)
app.get("请求URL",function(req,res){
//处理函数
})
3、GET请求中的参数
###req.query:获取URL中携带的参数
如:req.query.username or req.query.password
##req.params:获取URL中携带的动态参数
如:app.get("/user/:id/:name",(req,res)=>{
res.send(req.params);
})
请求url:http://localhost:80/user/1/lee
返回:
{
“id":1,
"name":"lee"
}
例子:
//引入express
const express = require("express");
//创建app
const app = express();
//监听get请求
app.get("/user/list", (req, res) => {
res.send(req.query);
});
app.get("/user/:id/:name", (req, res) => {
res.send(req.params);
});
//监听80端口
app.listen(80, () => {
console.log("the express server running at http://localhost:80");
});
4、监听POST请求
//req : 请求对象(包含了与请求相关的属性和方法)
//res : 响应对象(包含了与响应相关的属性和方法)
app.post("请求URL",function(req,res){
//处理函数
})
5、POST请求中的参数
##req.body 获取post请求中的参数
如:req.body.username or req.body.password
#注意:
》 处理application/json数据
app.use(express.json());
》处理application/x-www-form-urlencoded数据
app.use(express.urlencoded({ extended: true }));
例子:
//引入express
const express = require("express");
//创建app
const app = express();
//监听post请求 (application/json请求)
// app.use(express.json());
// app.post("/user/add", (req, res) => {
// let id = req.body.id;
// let name = req.body.name;
// let age = req.body.age;
// //res.send(`this post params is id=${id},name=${age},age=${age}`);
// res.send(req.body);
// });
//监听post请求 (application/x-www-form-urlencoded请求)
app.use(express.urlencoded({ extended: true }));
app.post("/animal/add", (req, res) => {
res.send(req.body);
})
//监听80端口
app.listen(80, () => {
console.log("the express server running at http://localhost:80");
})
6、返回响应
res.send(响应内容);
四、托管静态资源
##express.static()
express.static(),通过它,我们可以方便地创建一个静态资源服务器。
例如:通过如下代码将public目录下的图片、css文件、javascript文件对外开放访问:
app.use(express.static("./public"))
##注意:
express在指定的静态目录中查找文件,并对外提供资源的访问路径。
因此,存放静态文件的目录不会出现在URL中
托管多个静态资源,多次调用express.static()方法就行,如果多个目录中有相同的文件则按托管的顺序进行优先级访问。
##挂载路径前缀(可以解决多个托管目录中的存在相同文件的问题)
app.use("/mypublic",express.static("./public"));
五、路由
在express中,路由是指客户端的请求与服务器处理函数之间的映射关系。
express中的路由由3部分组成:【请求类型】、【请求URL地址】、【处理函数】
格式如下:
app.METHOD(PATH,HANDLER);
其中METHOD即 get和post
path 指 URL
HANDLER 指 回调函数
1、路由匹配顺序
》 按照定义的先后顺序进行匹配
》 请求类型 和 请求的URL 必须同时匹配成功,才会调用对应的处理函数
2、最简单的使用
最简单的使用就是挂载到app上
//引入express
const express = require("express");
//创建web
const app = express();
//挂载路由
app.get("/user",(req,res)=>{
//处理。。。
});
app.post("/modifyUser",(req,res)=>{
//处理。。。
})
//监听80端口
app.listen(80, () => {
console.log("the express server running at http://localhost:80 ");
});
3、模块化路由
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。
步骤:
》 创建路由模块对应的js文件
》 调用 express.Router() 函数创建路由对象
》 向路由对象上挂载具体的路由 如: router.get router.post
》 使用module.exports 向外共享路由对象 module.exports = router
》 使用app.use() 函数注册路由模块
const express = require("express");
const router = express.Router();
router.get("/user/list",(req,res)=>{
res.send("get user list.");
})
router.post("/user/add",(req,res)=>{
res.send("add user .");
})
module.exports = router;
4、注册路由模块
//导入路由模块
const userRouter = require("./router/user.js");
//注册路由模块
app.use(userRouter);
注意:
app.use() 是用来注册全局中间件的
5、为路由添加前缀
app.use("userapi",userRouter);
六、中间件
当一个请求到达express服务器后,可以连续调用多个中间件进行预处理
express的中间件,本质上就是一个function处理函数,express中间件的格式如下:
function(req,res,next){
//... 处理业务逻辑
next();
}
中间件函数的形参列表中,必须包含next参数。而路由处理函数中只包含req和res
1、next
next 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
2、全局生效的中间件
客户端发起的任何请求,到达服务器后,都会触发的中间件,叫做全局生效的中间件。
通过:
app.use(中间件函数)
即可定义一个全局生效的中间件
如:
const mw = function(req,res,next){
console.log(这是一个简单的中间件函数);
next();
}
//使中间件全局生效
app.use(mw);
##简化写法:
app.use(function(req,res,next){
console.log("this is my first middleware");
next();
})
或
app.use((req,res,next)=>{
console.log("this is my second middleware");
next();
})
例子:
const express = require("express");
const app = express();
const mw = function (req, res, next) {
console.log("this is a simple middleware");
next();
}
//使中间件全局生效
app.use(mw);
app.get("/user/list", function (req, res) {
console.log("this is a get query");
res.send("user list page");
});
app.listen(80, () => {
console.log("the express server running at http://localhost:80");
})
##结果:
the express server running at http://localhost:80
this is a simple middleware
this is a get query
##注意:
如果把app.get()放在 mw上面的话,打印的就只有 this is a get query. 中间件的内容却没有执行
3、中间件的作用
多个中间件之间,共享同一份 req 和 res。
基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
可以把它理解成 spring中的 intercepter
例子:
const express = require("express");
const app = express();
//中间件,给req添加一个时间参数
app.use((req, res, next) => {
req.startTime = Date.now();
next();
});
app.get("/user", (req, res) => {
res.send(`this query time is ${req.startTime}`);
});
app.listen(80, () => {
console.log("this express server running at http://localhost:80");
});
4、多个中间件
可以使用 app.use0 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用
如:
app.use((req,res,next)=>{
console.log("1");
next();
});
app.use((req,res,next)=>{
console.log("2");
next();
});
5、局部生效的中间件
不使用 app.use() 定义的中间件,叫做局部生效的中间件.
如:
//定义一个局部生效的中间件
const pmw = function (req, res, next) {
console.log("this is a part middleware function");
next();
}
//全局中间件要使用app.use(pmw); 局部局部中间件不需要
//中间件会影响这个路由
app.get("/a", pmw, (req, res) => {
res.send("hehe");
})
//中间件不会影响这个路由
app.get("/b", (req, res) => {
res.send("heihei");
})
6、多个局部生效的中间件
如:
app.get("/",mw1,mw2,(req,res)=>{});
app.get("/",[mw1,mw2],(req,res)=>{});
7、中间件使用注意事项
> 一定要在注册路由之前注册中间件
> 客户端发送过来的请求,可以连续调用多个中间件进行处理
> 执行完中间件的业务代码后,记得调用next()函数
> 为防止代码逻辑混乱,调用完next()函数后,不要再写代码逻辑了
> 连续调用多个中间件时,多个中间件之间,共享同一个request和response对象
> 错误级别的中间件,必须注册在所有路由之后
8、中间件的分类
》 应用级别的中间件
》 路由级别的中间件
》 错误级别的中间件
》 express内置的中间件
》 第三方的中间件
8.1、应用级别的中间件
通过 app.use() 或app.get() 或 app.post(),绑定到 app 实例上的中间件,叫做应用级别的中间件
如:
app.use((req,res,next)=>{
console.log("全局中间件");
next();
})
const partmw = (req,res,next)=>{
console.log("局部中间件");
next();
}
app.get("/",partmw,(req,res)=>{
console.log("xxxxx");
})
8.2、路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。
它的用法和应用级别中间件没有任何区别。
只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上.
如:
const app = express();
const router = express.Router();
//路由级别的中间件
router.use(function(req,res,next){
console.log("xxxx");
next();
})
app.use("/",router)
8.3、错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:
错误级别中间件的function 处理函数中,必须有4个形参,形参顺序从前到后,分别是(err,req,res,next)。
注意:
错误级别的中间件必须注册在所有路由之后
如:
app.get("/",(req,res)=>{
throw new Error("手动抛出一个错误!");
res.send("this is home page"); //上面抛出错误后,这里就不会再执行了
})
//这是一个错误级别的中间件
app.use((error,req,res,next)=>{
console.log("服务器发生了错误 "+error.message);
res.send("Error : "+error.message);
});
8.4、express内置中间件
》 express.static 快速托管静态资源的内置中间件。(没有兼容性)
》 express.json 解析JSON格式的请求数据 (仅在4.16.0+的版本后使用)
》 express.urlencoded 解析URL-encoded格式的请求数据(仅在4.16.0+的版本后使用)
》
如:
//配置解析 application/json格式数据的内置中间件
app.use(express.json());
//有了它之后 路由中才可以使用 req.body获取参数,否则是undefined
//配置解析 application/x-www-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded({extended:false}));
8.5、第三方中间件
如:在[email protected]之前使用 body-parser这个第三方中间件来解析请求体数据。
步骤如下:
> npm i body-parser安装中间件
> 使用require导入中间件
> 调用app.user()注册并使用中间件
如:
const parser = require("body-parser");
//注意express内置的express.urlencoded就是基于body-parser封装出来的
app.use(parser.urlencoded({extended:false}));
9、自定义中间件
需求:
手动模拟一个express.urlencoded中间件,来解析post提交到服务器的表单数据
步骤:
》 定义中间件
》 监听 req 的 data 事件
》 监听 req 的 end 事件
》 使用 queryString 模块解析请求体数据
》 将解析出来的数据对象挂载为 req.body
》 将自定义中间件封装为模块
9.1、req的data事件
在中间件中,需要监听req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果请求数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。
所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。
let reqStr = "";
req.on("data",(chunk)=>{
reqStr+=chunk;
});
9.2、req的end事件
当请求体数据接收完毕之后,会自动出发req的end事件。
因此,我们可以在req的end实践中,拿到并处理完整的请求体数据。
req.on("end",()=>{
//...处理
});
9.3、querystring模块
node.js内置了一个querystring模块,专门用来处理查询字符串。
通过这个模块提供的parse()函数,可以轻松把查询字符串,解析成对象的格式。
const qs = require("querystring");
//将字符串解析为对象
const body = qs.parse("str");
9.4、测试:原始请求
const express = require("express");
const app = express();
//监听user请求
app.post("/user", (req, res) => {
res.send(req.body);
});
app.listen("80", () => {
console.log("this server is running on http://127.0.0.1:80");
})
node.js自带的处理post参数是按如下方式处理的。
》 处理application/json数据
app.use(express.json());
》处理application/x-www-form-urlencoded数据
app.use(express.urlencoded({ extended: true }));
9.5、中间件处理
const express = require("express");
const qs = require("querystring");
const app = express();
//中间件处理请求参数
app.use((req, res, next) => {
//1、req如果请求数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。
//需要监听req 对象的 data 事件
let reqStr = "";
req.on("data", (chunk) => {
reqStr += chunk;
});
//2、请求数据接收完后会自动触发end事件,我们就在end事件里处理入参
req.on("end", () => {
console.log(reqStr);
let myBody = qs.parse(reqStr);
console.log(myBody);
//3、将myBody挂载到req上
req.body = myBody;
next();
});
});
//监听user请求
app.post("/user", (req, res) => {
res.send(req.body);
});
app.listen("80", () => {
console.log("this server is running on http://127.0.0.1:80");
});
9.6、中间件封装为模块
测试
const express = require("express");
const app = express();
//中间件处理请求参数
const myBodyPaser = require("./my-custom-body-parser");
app.use(myBodyPaser);
//监听user请求
app.post("/user", (req, res) => {
res.send(req.body);
});
app.listen("80", () => {
console.log("this server is running on http://127.0.0.1:80");
});
中间件模块
const qs = require("querystring");
const myBodyPaser = (req, res, next) => {
//1、req如果请求数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。
//需要监听req 对象的 data 事件
let reqStr = "";
req.on("data", (chunk) => {
reqStr += chunk;
});
//2、请求数据接收完后会自动触发end事件,我们就在end事件里处理入参
req.on("end", () => {
console.log(reqStr);
let myBody = qs.parse(reqStr);
console.log(myBody);
//3、将myBody挂载到req上
req.body = myBody;
next();
});
}
//将模块暴露出去
module.exports = myBodyPaser;
七、综合:使用Express写接口
步骤:
》 创建基本的web服务器
》 创建API路由模块
》 编写GET和POST请求
测试
const express = require("express");
const app = express();
//注意这个要写在外层而不是路由模块里
app.use(express.json());
//绑定路由
const myRouter = require("./myRouter")
app.use("/api", myRouter);
app.listen("80", () => {
console.log("this server is running at http://127.0.0.1:80");
})
路由模块
const express = require("express");
const router = express.Router();
router.get("/user/queryInfoById", (req, res) => {
let data = req.query;
res.send({
"code": "200",
"message": "GET请求成功",
"data": data
});
});
router.post("/user/add", (req, res) => {
let data = req.body;
console.log(data);
res.send({
"code": "200",
"message": "POST请求成功",
"data": data
});
})
module.exports = router;