Express

简介

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

中文官网

express中文官网

使用

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

express初体验

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请求都可以访问;因为没有对路由做限制,所以所有路由都可以访问;

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();

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()调用栈中的下一个可以匹配成功的中间件;

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不做限制

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即可

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不支持)

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

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();

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接收参数;

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初使用

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():
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");
})

文件上传

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

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):磁盘存储引擎可以让你控制文件的存储。
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可以为字符串也可以为函数,但是两者使用上有区别:

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))
}
})
  • 字符串,将根据字符串,创建文件夹;
  • 回调函数中,传入的字符串表示文件放入的地址不会创建该文件夹(若没有的话);

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

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:

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

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

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解析

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解析

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路由

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实例拥有完整的中间件和路由系统;

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;
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(“/公共路由”,”路由”);上面的写法就可以写成下面这样:

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;
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信息);
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可以传递 错误信息
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库学习