# Express
# express 介绍
- Express 是一个第三方模块,用于快速搭建服务器(替代http模块)
- Express 是一个基于 Node.js 平台,快速、开放、极简的 web 开发框架。
- Express保留了http模块的基本API,使用express的时候,也能使用http的API
- 使用express的时候,仍然可以使用http模块的方法,比如 res.end()、req.url
- express还额外封装了一些新方法,能让我们更方便的搭建服务器
- express提供了中间件功能,其他很多强大的第三方模块都是基于express开发的
- Express 官网 (opens new window)
- Express 中文文档(非官方) (opens new window)
- Express GitHub仓库 (opens new window)
- 菜鸟教程 (opens new window)
- 腾讯云开发者手册 (opens new window)
- 百度自行搜索
# 安装 express
项目文件夹中,执行 npm i express。即可下载安装express。
注意:express不能安装在express文件夹中。否则安装失败。
# 使用Express构造Web服务器
使用Express构建Web服务器步骤:
加载 express 模块
创建 express 服务器
开启服务器
监听浏览器请求并进行处理
// 使用express 搭建web服务器
// 1) 加载 express 模块
const express = require('express');
// 2) 创建 express 服务器
const app = express();
// 3) 开启服务器
app.listen(3006, () => console.log('express服务器开始工作了'));
// 4) 监听浏览器请求并进行处理
app.get('GET请求的地址', 处理函数);
app.post('POST请求的地址', 处理函数);
# express封装的新方法
express之所以能够实现web服务器的搭建,是因为其内部对核心模块http进行了封装。
封装之后,express提供了非常方便好用的方法。
比如前面用到的 app.get() 和 app.post() 就是express封装的新方法。
下面再介绍一个 res.send() 方法
- 该方法可以代替之前的 res.end 方法,而且比 res.end 方法更好用
- res.send() 用于做出响应
- 响应的内容同样不能为数字
- 如果响应的是JS对象,那么方法内部会自动将对象转成JSON格式。
- 而且会自动加Content-Type响应头
- 如果已经做出响应了,就不要再次做出响应了。
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));
// 写接口
app.get('/api/test', (req, res) => {
// res.end('hello world,哈哈哈'); // 响应中文会乱码,必须自己加响应头
// res.end(JSON.stringify({ status: 0, message: '注册成功' })); // 只能响应字符串或者buffer类型
// express提供的send方法,可以解决上面的两个问题
res.send({ status: 0, message: '注册成功' }); // send方法会自动设置响应头;并且会自动把对象转成JSON字符串
});
请注意,在express中,我们仍然可以使用http模块中的方法和属性,比如req.url。
# 案例 - 大事件的登录注册接口
# 创建数据表
| 字段 | 类型 | 长度 | 不是null | 主键 | 其他 |
|---|---|---|---|---|---|
| id | int | √ | 🔑 | √ 自动递增 | |
| username | varchar | 10 | √ | ||
| password | char | 32 | √ | ||
| user_pic | longtext | ||||
| nickname | varchar | 10 | |||
| varchar | 30 |
# 使用ApiPost模拟注册请求

# 写接口
// 完成接口项目
// 前面三行启动服务
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));
// 配置 + 写接口
// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
// 1. 接口要接收数据
// 2. 判断账号是否已经被占用了
// 3. 如果没有被占用,把账号密码添加到数据库
});
# 服务端使用 req.body 接收请求体
请求体就是客户端提交的数据(username和password)。
// 完成接口项目
// 前面三行启动服务
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));
// 配置 + 写接口
app.use(express.urlencoded({ extended: true }));
// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
// 1. 接口要接收数据
console.log(req.body); // { username: 'laotang', password: '123456' }
// 2. 判断账号是否已经被占用了
// 3. 如果没有被占用,把账号密码添加到数据库
});
代码写完,一定要使用ApiPost发送请求,测试代码。
# 验证用户名是否存在
思路:根据用户名查询,看是否能够查到数据。
- 没有查询数据,说明这个用户名不存在,能够使用
- 如果查到数据库,说明这个用户名已经存在,不能使用
// 完成接口项目
// 前面三行启动服务
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));
// 配置 + 写接口
app.use(express.urlencoded({ extended: true }));
// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
// 1. 接口要接收数据
console.log(req.body); // { username: 'laotang', password: '123456' }
let { username, password } = req.body;
// 2. 判断账号是否已经被占用了
db('select * from user where username="${username}"', (err, result) => {
if (err) throw err;
// console.log(result); // 查到信息,result是非空数组;没有查到信息,result是空数组
if (result.length > 0) {
res.send({ status: 1, message: '用户名被占用了' });
} else {
// 没有被占用
// 3. 如果没有被占用,把账号密码添加到数据库
}
})
});
# 完成注册
如果用户名可用,则添加到数据表中,完成注册
// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
// 1. 接口要接收数据
// console.log(req.body); // { username: 'laotang', password: '123456' }
let { username, password } = req.body;
// 2. 判断账号是否已经被占用了
db(`select * from user where username='${username}'`, (err, result) => {
if (err) throw err;
// console.log(result); // 查到信息,result是非空数组;没有查到信息,result是空数组
if (result.length > 0) {
res.send({ status: 1, message: '用户名被占用了' });
} else {
// 没有被占用
// 3. 如果没有被占用,把账号密码添加到数据库
db(`insert into user set username='${username}', password='${password}'`, (e, r) => {
if (e) throw e;
res.send({ status: 0, message: '注册成功' });
});
}
});
});
# 对密码进行md5加密
安全起见,数据表中不能存储明文密码。必须存储加密后的密码,而且应该使用一种不可逆的加密方案。
常用的加密方式是 md5。
下载安装第三方加密模块,并解构里面的 md5 方法
let { md5 } = require('utility')对密码进行加密
password = md5(password)
# 案例 - 大事件类别管理案例
目前只完成接口功能即可,不需要考虑token 的问题。
# 启动服务,把接口的骨架完成
创建了index.js
// 三个步骤,启动服务
var express = require('express');
var app = express();
app.listen(8888, function () {
console.log('服务器启动了')
});
// 配置 + 写接口
// ----------------- 获取分类的接口 ------------------
/**
* 请求方式:GET
* 接口地址: /my/category/list
*/
app.get('/my/category/list', (req, res) => {
});
// ----------------- 删除分类的接口 ------------------
/**
* 请求方式:GET
* 接口地址: /my/category/delete
* 请求参数: id(分类id)
*/
app.get('/my/category/delete', (req, res) => {
});
// ----------------- 添加分类的接口 ------------------
/**
* 请求方式:POST
* 接口地址: /my/category/add
* 请求参数: name(类别名称) | alias(类别别名)
*/
app.post('/my/category/add', (req, res) => {
});
// ----------------- 修改分类的接口 ------------------
/**
* 请求方式:POST
* 接口地址: /my/category/update
* 请求参数: name(类别名称) | alias(类别别名) | id(分类的id)
*/
app.post('/my/category/update', (req, res) => {
});
# 设计数据表并添加模拟数据
创建 category表
| 字段 | 类型 | 长度 | 不是null | 主键 | 自动递增 |
|---|---|---|---|---|---|
| id | int | √ | 🔑 | √ | |
| name | varchar | 10 | √ | ||
| alias | varchar | 10 | √ |
自行添加几条模拟数据。
# 封装db.js 工具
把之前封装好的 db.js 复制过来即可。
function db (sql, params, cb) {
var mysql = require('mysql');
var conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '12345678',
database: 'sy120'
});
conn.connect();
conn.query(sql, params, cb);
conn.end();
}
module.exports = db;
# 完成获取分类列表数据的接口
// ----------------- 获取分类的接口 ------------------
/**
* 请求方式:GET
* 接口地址: /my/category/list
*/
app.get('/my/category/list', (req, res) => {
// 调用db函数,查询所有的分类
db('select * from category', null, function (err, result) {
if (err) throw err;
// 没有错误的话,做出响应
res.send({
status: 0,
message: '获取分类成功',
data: result
});
});
});
# 删除分类的接口
客户端发送请求,并且传递id参数

服务端代码:
// ----------------- 删除分类的接口 ------------------
/**
* 请求方式:GET
* 接口地址: /my/category/delete
* 请求参数: id(分类id)
*/
app.get('/my/category/delete', (req, res) => {
// 获取id参数
var id = req.query.id;
// 删除数据表中的数据
db('delete from category where id=?', id, function (err, result) {
// 做出响应
if (err) throw err;
if (result.affectedRows > 0) {
res.send({status: 0, message: '删除分类成功'});
} else {
res.send({status: 1, message: '删除分类失败'})
}
});
});
# 添加分类的接口

服务端代码:
// 解析POST参数(接收POST参数,并转成成对象,然后把转换后的结果赋值给req.body)
app.use(express.urlencoded({ extended: true }));
// ----------------- 添加分类的接口 ------------------
/**
* 请求方式:POST
* 接口地址: /my/category/add
* 请求参数: name(类别名称) | alias(类别别名)
*/
app.post('/my/category/add', (req, res) => {
// 1. 接收客户端提交的数据(name和alias)
// console.log(req.body); // { name: '娱乐', alias: 'yule' }
// 2. 添加到数据库
db('insert into category set ?', req.body, function (err, result) {
if (err) throw err;
// 3. 做出响应
res.send({ status: 0, message: '添加分类成功' })
});
});
# 更新分类接口

服务端代码:
/**
* 请求方式:POST
* 接口地址: /my/category/update
* 请求参数: name(类别名称) | alias(类别别名) | id(分类的id)
*/
app.post('/my/category/update', (req, res) => {
// 1. 接收客户端提交的数据(id、name、alias)
// console.log(req.body); // { id: '1', name: '科技', alias: 'keji' }
// 2. 执行update语句,修改数据
db('update category set ? where id=?', [req.body, req.body.id], function (err, result) {
if (err) throw err;
if (result.affectedRows > 0) {
res.send({ status: 0, message: '修改分类成功' })
} else {
res.send({ status: 1, message: '修改分类失败' })
}
})
});
# Express路由
路由:即请求和处理程序的映射关系。
使用路由的好处:
- 降低匹配次数,提高性能
- 分类管理接口,更易维护与升级
使用步骤:
/**
* 使用路由文件的步骤
* 1. 加载express模块
* 2. 创建 router 对象
* 3. 把接口挂载到 router 对象上
* 4. 导出 router 对象
*
* app.js 中
* 5. 加载路由模块,并注册成中间件
*/
- 注意事项:
- 路由文件如果没有导出 router,那么在 入口文件中不要注册中间件,否则报错
- 哪个路由文件中使用了db,自己加载(谁用谁加载)

# 中间件介绍
- 中间件(Middleware ),特指业务流程的中间处理环节。
- 中间件,是express最大的特色,也是最重要的一个设计
- 很多第三方模块,都可以当做express的中间件,配合express,开发更简单。
- 一个express应用,是由各种各样的中间件组合完成的
- 中间件,本质上就是一个函数
# 中间件原理
为了理解中间件,我们先来看一下我们现实生活中的自来水厂的净水流程。

- 在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节
- 我们称其中的每一个处理环节就是一个中间件。
- 这样做的目的既提高了生产效率也保证了可维护性。
express中间件原理:

# 中间件的几种形式
// 下面的中间件,只为当前接口 /my/userinfo 这个接口服务
app.get('/my/userinfo', 中间件函数);
// 下面的几个中间件,是处理 /api/login 接口的
app.post('/api/login', 中间件函数, 中间件函数, 中间件函数, 中间件函数 .....);
// app.use 中的中间件,可以处理所有的GET请求和所有的POST请求,没有指定路径,那么处理所有接口
app.use(中间件函数);
// 下面的中间件函数,只处理 /api 开头的接口
app.use('/api', 中间件函数);
// 下面的中间件函数,处理 /abcd 、 /abd 这两个接口
app.use('/abc?d', 中间件函数);
app.get或者app.post表示写接口,必须写接口地址;
app.use() 参数1路由前缀,可以省略。另外无论是GET还是POST方式的请求,都会进入该中间件。
# 中间件语法
- 中间件就是一个函数
- 中间件函数中有四个基本参数, err、req、res、next
- 如果写两个参数,那么两个参数肯定是 req和res
- 如果写三个参数,那么三个参数肯定是 req,res和next
- 如果写四个参数,那么就是全部的参数。
- 把写好的中间件函数,传递给
app.get()、app.post()、或app.use()使用
# 中间件的特点
- 每个中间件函数,共享req对象、共享res对象
- js代码中,所有的req对象是一个对象;所有的res是一个对象
- 不调用
next(),则程序执行到当前中间件函数后,不再向后执行- 注意中间件的顺序,因为有可能因为顺序原因,你的中间件函数不会执行
- 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
- 客户端发送过来的请求,可能连续调用多个中间件进行处理
- 使用
app.use()注册的中间件,GET和POST请求都可以触发 - 错误处理中间件,必须传递 err、req、res、next四个参数,而且要放到所有接口的后面
- 如果前面的中间件,没有给next传参,并且代码也没有错误,请求将不会进入到错误处理中间件
- 如果前面的中间件,给next传递了实参(无论是什么实参),程序会绕过后面的所有中间件,直接进入到最后的错误处理中间件。
# 中间件分类
- 应用级别的中间件
- 路由级别的中间件
- 错误处理中间件
- 内置中间件(express自带的,比如
express.urlencoded({ extended: true })) - 第三方中间件(比如multer、express-jwt、express-session、jsonwebtoken....)
实际开发中,自己写中间件的机会并不大,一般都有对应的第三方中间件。