Express

简介

  • Express 是一个简洁而灵活的 Node.js Web 应用框架,提供一系列强大特性帮助你创建各种 Web 应用
  • Express 内部还是使用的 http 模块实现服务器创建和监听,对 http 模块进行了二次封装
  • 严格上来说 express 就是 NodeJS 的一个模块 直接下载安装

中文官网

express 中文官网

使用

  • node 下载 express 模块:
  • 命令 :
    • 初始化服务 : npm init -y
    • 下载 express: npm i express
  • 使用的时候 引入 需要实例化一个 express 应用 使用应用对象创建各种服务

express 初体验

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const express = require("express");
// express其实是一个函数;
// 调用函数返回 app,app本质也是一个函数;
const app =express();

// 监听默认路径(GET)
app.get("/",(req,res,next)=>{
res.end("hello GET express");
})

// 监听默认路径(POST)
app.post("/",(req,res,next)=>{
res.end("hello POST express");
})

// 开启监听
app.listen(3000,()=>{
console.log("server is running port is 3000");
})

express 中间件

中间件基本概念

Express 是一个路由和中间件的 Web 框架,Express 应用程序本质上是一系列中间件函数的调用;那么,中间件是什么呢?

  • 中间件的本质是传递给 express 的一个回调函数;
  • 这个回调函数接受三个参数:
  • 请求对象(request 对象);
  • 响应对象(response 对象);
  • next 函数(在 express 中定义的用于执行下一个中间件的函数);

中间件中可以执行哪些任务呢?

  • 执行任何代码;
  • 更改请求(request)和响应(response)对象;
  • 结束请求 - 响应周期(返回数据);
  • 调用栈中的下一个中间件;
  • 如果当前中间件功能没有结束请求 - 响应周期,则必须调用 next () 将控制权传递给下一个中间件功能,否则,请求 将被挂起。

app.use 创建最普通中间件 (对 method 和路由均没有限制)

使用 app.use 注册一个最普通的中间件,因为没有对 method 做限制,所以 GET请求POST请求都可以访问;因为没有对路由做限制,所以所有路由都可以访问;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const express = require("express");

const app = express();

// 编写普通中间件
// use注册一个中间件(回调函数)
app.use((req,res,next)=>{
console.log("注册了第01个普通中间件");
res.end("over");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

app.use 创建多个最普通中间件

app.use 注册多个最普通的中间件,当发送请求的时候,虽然这几个中间件都可以匹配成功,但是只会执行第 01 个中间件;后面的即使匹配成功了,也不会执行;

即:中间件永远查找匹配成功的第一个中间件,要想别的匹配成功的中间件也执行,需要在上一个匹配成功的中间件执行 next ();

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require("express");

const app = express();

// 编写普通中间件
// use注册一个中间件(回调函数)
app.use((req, res, next) => {
console.log("注册了第01个普通中间件");
res.end("over");
})

app.use((req, res, next) => {
console.log("注册了第02个普通中间件");
})

app.use((req, res, next) => {
console.log("注册了第03个普通中间件");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

那么如何执行多个中间件呢?

答:使用 next() 调用栈中的下一个可以匹配成功的中间件;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require("express");

const app = express();

// 编写普通中间件
// use注册一个中间件(回调函数)
app.use((req, res, next) => {
console.log("注册了第01个普通中间件");
res.end("over");
next(); //调用了下一个匹配成功的中间件
})

app.use((req, res, next) => {
console.log("注册了第02个普通中间件");
next(); //调用了下一个匹配成功的中间件
})

app.use((req, res, next) => {
console.log("注册了第03个普通中间件");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

路由中间件

路径匹配中间件:对路由做限制,对 method 不做限制

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require("express");

const app = express();

// 路径匹配中间件:对路由做限制,对method不做限制
/**
* 以下均可匹配成功
* http://127.0.0.1:3000/home
* http://127.0.0.1:3000/home?username=fwd&password=123456
*/
//

app.use("/home",(req,res,next)=>{
console.log("home middleware 01");
res.end("home middleware");
})


app.listen(3000, () => {
console.log("server is running port is 3000");
})

多个路由中间件同上,需要调用 next () 执行下一个匹配成功的中间件;

路由和方法都匹配的中间件

不使用 app.use 即可

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require("express");

const app = express();

app.get("/home",(req,res,next)=>{
console.log("home path and method middleware 01");
res.end("home middleware");
})

app.post("/login",(req,res,next)=>{
console.log("login path and method middleware 01");
res.end("login middleware");
})


app.listen(3000, () => {
console.log("server is running port is 3000");
})

连续注册中间件 (koa 不支持)

多个参数,每个参数都是回调函数

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require("express");

const app = express();

// 这样其实也只会匹配第一个匹配的中间件,即打印 home path and method middleware 01
app.get("/home", (req, res, next) => {
console.log("home path and method middleware 01");
res.end("home middleware");
}, (req, res, next) => {
console.log("home path and method middleware 02");
}, (req, res, next) => {
console.log("home path and method middleware 03");
}, (req, res, next) => {
console.log("home path and method middleware 04");
})


app.listen(3000, () => {
console.log("server is running port is 3000");
})

要想执行多个匹配成功的中间件,也还是调用 next ();

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require("express");

const app = express();


app.get("/home", (req, res, next) => {
console.log("home path and method middleware 01");
res.end("home middleware");
next();
}, (req, res, next) => {
console.log("home path and method middleware 02");
next();
}, (req, res, next) => {
console.log("home path and method middleware 03");
next();
}, (req, res, next) => {
console.log("home path and method middleware 04");
})


app.listen(3000, () => {
console.log("server is running port is 3000");
})

POST 请求 body 参数解析

中间件应用 - json-urlencoded 解析

注册 express 内置的 body-parser;使用 req.body 接收参数;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const express = require("express");
const app = express();

// 使用body-parser express内置成函数 即直接注册

app.use(express.json()); //解析 JSON数据
app.use(express.urlencoded({extended:true})); //解析 x-www-form-urlencoded 数据


app.post("/login",(req,res,next)=>{
const info = req.body;
console.log(info);
res.end("使用req.body获取body参数");
})
app.listen(3000, () => {
console.log("server is running port is 3000");
})

关于 express.urlencoded ({extended:true}) 中的 extended:

  • true: 那么对 urlencoded 进行解析时,他使用的是第三方库:qs
  • false:那么对 urlencoded 进行解析时,他使用的是 node 内置模块:querystring

中间件应用 - form-data 解析

form-data 解析:一般用于上传图片文件;

express 框架没有内置的解析中间件,但是有官方的库 multer,需要下载安装一下:npm i multer

初步使用

multer初使用

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//不推荐此写法
const express = require("express");
const app = express();
// 引入multer,是个函数
const multer = require("multer");

//调用multer函数
const upload = multer();

// 表示 form-data中普通的数据
app.use(upload.any());


app.post("/upload",(req,res,next)=>{
console.log(req.body);
res.end("用户上传成功");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

但是上方写法存在个问题:https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md 写着” 永远不要将 multer 作为全局中间件使用,因为恶意用户可以上传文件到一个你没有预料到的路由,应该只在你需要处理上传文件的路由上使用;

  • 如果你需要处理一个只有文本域的表单,你应当使用 .none():
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require("express");
// 引入multer,是个函数
const multer = require("multer");
const app = express();

const upload = multer()
// 如果你需要处理一个只有文本域的表单,你应当使用 .none():
app.post("/upload", upload.none(), (req, res, next) => {
// req.body 包含文本域
console.log(req.body);
res.end("用户上传成功~");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

文件上传

上传图片文件,并且将上传的图片文件放在服务器中某位置:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require("express");
// 引入multer,是个函数
const multer = require("multer");
const app = express();

// 表示将上传的文件指定地址存放,若没有该文件,则创建文件夹(启动服务的时候,没有的话会创建文件夹)
const upload = multer({
dest: "./uploadImg/"
});

// 这里使用连续中间件,upload.single('img')表示解析单个文件夹,且key值为 "img",
// 进行处理后,传递给后面的中间件 next ,然后返回结果给客户端
app.post("/upload", upload.single("avatar"), (req, res, next) => {
// req.file 是 `avatar` 文件的信息
// req.body 将具有文本域数据,如果存在的话
console.log(req.file);
console.log(req.body);
res.end("用户上传成功~");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

此时我们图片没有后缀,而且不能直接打开,我们给图片添加后缀和重命名

  • 磁盘存储引擎 (DiskStorage):磁盘存储引擎可以让你控制文件的存储。
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const express = require("express");
const multer = require("multer");
const path = require("path");
const app = express();

const storage = multer.diskStorage({
// 回调函数写法 这个文件夹必须自己手动创建,没有的话也不会自动创建;
destination: (req, file, cb) => {
cb(null, "./uploadImg/")
},
filename: (req, file, cb) => {
// path.extname(file.originalname))获取文件的拓展名 .png .jpg .gif
cb(null, Date.now() + path.extname(file.originalname));
}
})
const upload = multer({
storage
});

// 这里使用连续中间件,upload.single('img')表示解析单个文件夹,且key值为 "img",
// 进行处理后,传递给后面的中间件 next ,然后返回结果给客户端
app.post("/upload", upload.single("avatar"), (req, res, next) => {
// req.file 是 `avatar` 文件的信息
// req.body 将具有文本域数据,如果存在的话
console.log(req.file);
console.log(req.body);
res.end("用户上传成功~");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

这里使用 diskStorage 处理文件上传的地址和文件名,其中 destination 可以为字符串也可以为函数,但是两者使用上有区别:

js
1
2
3
4
5
6
7
8
9
10
11
12
// 为上传的文件添加后缀和文件名
const storage = multer.diskStorage({
// cb是callback回调函数的缩写
// destination:(req,file,cb)=>{
// cb(null,'./uploadImg/')
// },
destination:"./uploadImg/",
filename:(req,file,cb)=>{
// 文件名取 时间戳和原文件的后缀名进行组合
cb(null,Date.now()+path.extname(file.originalname))
}
})
  • 字符串,将根据字符串,创建文件夹;
  • 回调函数中,传入的字符串表示文件放入的地址不会创建该文件夹(若没有的话);

上面是一次上传一张图片的情形,其实还有一次上传多张图片的情形;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const express = require('express')
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })

const app = express()

app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file 是 `avatar` 文件的信息
// req.body 将具有文本域数据,如果存在的话
})

app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files 是 `photos` 文件数组的信息
// req.body 将具有文本域数据,如果存在的话
})

const cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files 是一个对象 (String -> Array) 键是文件名,值是文件数组
//
// 例如:
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body 将具有文本域数据,如果存在的话
})
  • .single (fieldname):接受一个以 fieldname 命名的文件。这个文件的信息保存在 req.file。
  • .array (fieldname [, maxCount]):接受一个以 fieldname 命名的文件数组。可以配置 maxCount 来限制上传的最大数量。这些文件的信息保存在 req.files。
  • .fields (fields):接受指定 fields 的混合文件。这些文件的信息保存在 req.files;fields 应该是一个对象数组,应该具有 name 和可选的 maxCount 属性。
  • array 和 fields 区别是:array 上传的多张文件名的 key 都是一样的,而 fields 因为参数是数组对象,所以可以多种 key,每个 key 又能限制 count;

Example:

js
1
2
3
4
[
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }
]

upload.any () 不作为全局,作为局部中间件;也可以接收一次上传多张的情形,而且还不用指定 key;更加自由;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const express = require("express");
const multer = require("multer");
const path = require("path");
const app = express();

//这个文件夹必须自己手动创建,没有的话也不会自动创建;
const storage = multer.diskStorage({
destination:"./uploadImg/",
filename: (req, file, cb) => {
// path.extname(file.originalname))获取文件的拓展名 .png .jpg .gif
cb(null, Date.now() + path.extname(file.originalname));
}
})
const upload = multer({
storage
});

// upload.any()作为中间件;
app.post("/upload", upload.any(), (req, res, next) => {
// req.files 是文件的信息 数组对象
// req.body 将具有文本域数据,如果存在的话
console.log(req.files);
console.log(req.body);
res.end("用户上传成功~");
})

app.listen(3000, () => {
console.log("server is running port is 3000");
})

GET 请求参数解析

params 解析 (路由参数)

/products/:id/:name 的这种的 (http://127.0.0.1:3000/products/fsl/forward),用 req.params 解析

js
1
2
3
4
5
6
7
8
9
10
11
12
13
const express= require("express");
const app = express();

// http://127.0.0.1:3000/products/fsl/forward
app.get("/products/:id/:name",(req,res,next)=>{
// 通过req.params接收,接收的是一个对象
console.log(req.params); //{ id: 'fsl', name: 'forward' }
res.end("商品的详情数据~")
})

app.listen(3000,()=>{
console.log("server is running port is 3000~");
})

query 解析

/login 这种的 (http://127.0.0.1:3000/login?name=fsl&age=18),用 req.query 解析

js
1
2
3
4
5
6
7
8
9
10
11
12
13
const express= require("express");
const app = express();

// http://127.0.0.1:3000/login?name=fsl&age=18
app.get("/login",(req,res,next)=>{
// 通过req.query接收,接收的是一个对象
console.log(req.query); // { name: 'fsl', age: '18' }
res.end("登陆成功~")
})

app.listen(3000,()=>{
console.log("server is running port is 3000~");
})

响应数据

  • res.end (xxx):只能返回 字符串、buffer 数据等,不能返回 JSON,数组;
  • res.json (xxx):可以传入很多的类型:object、array、string、boolean、number、null 等,它们会被转换成
    json 格式返回;
  • res.status (200):用于设置状态码

express 路由

js
1
2
3
4
5
6
7
8
/**
* 举个例子: 这些所有的放在一个app文件里,接口看起来是有点重复的,而且太多了,不方便管理;
* 请求所有的用户信息:get /users
* 请求某个用户信息: get /users/:id
* 创建某个用户信息: post /users body {username:fsllala,password:123}
* 删除某个用户信息: delete /users/:id
* 更新某个用户信息: put /users/:id
*/

我们可以使用 express.Router() 来创建一个路由处理程序:一个 Router 实例拥有完整的中间件和路由系统;

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//新建一个router文件夹,下面创建user.js
const express = require("express");
const router = express.Router(); //express.Router()是个函数

router.get("/users", (req, res, next) => {
res.json(["why", "kobe", "fsllala"]);
})

router.get("/users/:id", (req, res, next) => {
res.json(`${req.params.id}用户的信息`);
})

router.post("/users", (req, res, next) => {
res.json("create user success~");
})

module.exports = router;
js
1
2
3
4
5
6
7
8
9
//在app文件里引入路由
const express= require("express");
const app = express();
const userRouter = require("./router/user.js")
app.use(userRouter); //注册路由

app.listen(3000,()=>{
console.log("server is running port is 3000~");
})

注册路由的时候,可以有两个参数 app.use (“/ 公共路由”,” 路由”);上面的写法就可以写成下面这样:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//新建一个router文件夹,下面创建user.js
const express = require("express");
const router = express.Router(); //express.Router()是个函数

router.get("/", (req, res, next) => {
res.json(["why", "kobe", "fsllala"]);
})

router.get("/:id", (req, res, next) => {
res.json(`${req.params.id}用户的信息`);
})

router.post("/", (req, res, next) => {
res.json("create user success~");
})

module.exports = router;
js
1
2
3
4
5
6
7
8
9
//在app文件里引入路由
const express= require("express");
const app = express();
const userRouter = require("./router/user.js")
app.use("/users",userRouter); //注册路由

app.listen(3000,()=>{
console.log("server is running port is 3000~");
})

服务端错误处理

  • 处理方式一:不易维护 (后期修改 code 码 /message 信息);
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require("express");
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post("/login", (req, res, next) => {
const { username, password } = req.body;
if (!username || !password) {
res.json({
code: -1001,
message: "请输入用户名或密码!",
});
} else if (username !== "fsllala" || password !== "123456") {
res.json({
code: -1002,
message: "用户名或密码错误!",
});
} else {
res.json({
code: 0,
message: "登录成功!",
token:"fasfasf123123aff"
});
}
});

app.listen(3000, () => {
console.log("server is running~");
});
  • 处理方式二:通过 next () 传递给下一个匹配的中间件,其中 next 可以传递 错误信息
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const express = require("express");
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post("/login", (req, res, next) => {
const { username, password } = req.body;
if (!username || !password) {
// 通过next传递给下一个匹配的中间件,其中next可以传递 错误信息;
next(-1001);
} else if (username !== "fsllala" || password !== "123456") {
next(-1002);
} else {
res.json({
code: 0,
message: "登录成功!",
token:"fasfasf123123aff"
});
}
});

// 处理错误的中间件 (err接收next传递的错误信息)
app.use((err, req, res, next) => {
// console.log(err);
const code = err;
let message = "未知错误";
switch (err) {
case -1001:
message = "请输入用户名或密码!";
break;
case -1002:
message = "用户名或密码错误!";
break;
}
res.json({ code, message });
});

app.listen(3000, () => {
console.log("server is running~");
});

参考文献

multer 官方文档

Node 中间件 multer 库学习