Express 不同中间件类别的使用方式
应用级中间件
通过
app.use([path], middleware)或app.METHOD([path], middleware)注册。path是可选的,省略时对所有路径生效。
javascript
const express = require('express');
const app = express();
// 全局中间件:对所有请求生效
app.use((req, res, next) => {
console.log('Request URL:', req.originalUrl);
next(); // 必须调用 next() 将控制权交给下一个中间件
});
// 局部中间件:只对 /user 路径的 GET 请求生效
const specificMiddleware = (req, res, next) => {
console.log('Accessing /user endpoint');
next();
};
app.get('/user', specificMiddleware, (req, res) => {
res.send('User Info');
});
路由级中间件
使用
express.Router()创建一个路由实例,然后像使用app一样使用这个router实例。
javascript
const express = require('express');
const router = express.Router(); // 创建路由实例
// 在路由实例上使用中间件
router.use((req, res, next) => {
console.log('Time: ', Date.now());
next();
});
router.get('/list', (req, res) => {
res.send('User List');
});
// 最后将路由实例挂载到应用的某个路径下(如 /api)
const app = express();
app.use('/api', router); // 现在访问 /api/user/list 会触发上述中间件和路由
错误处理中间件
必须放在所有路由和其他中间件之后
javascript
// 在路由中抛出错误(同步)
app.get('/problematic', (req, res) => {
throw new Error('Something went wrong!'); // 同步错误直接 throw
});
// 在路由中传递错误(异步)
app.get('/async-problematic', (req, res, next) => {
someAsyncFunction()
.then(data => res.send(data))
.catch(error => next(error)); // 异步错误通过 next(error) 传递
});
// 定义在最后的错误处理中间件
app.use((err, req, res, next) => { // 注意4个参数
console.error(err.stack);
res.status(500).send('Something broke!');
});
内置中间件
Express 提供了几个开箱即用的中间件,最常见的是解析请求体和托管静态文件。
javascript
const express = require('express');
const app = express();
// 解析 application/json 格式的请求体数据
app.use(express.json());
// 解析 application/x-www-form-urlencoded 格式的请求体数据
app.use(express.urlencoded({ extended: false }));
// 托管静态文件,例如 public 目录下的图片、CSS、HTML
app.use(express.static('public'));
// 使用解析后的数据
app.post('/profile', (req, res) => {
// 配置了中间件后,req.body 才有值
console.log(req.body);
res.json({ message: 'Data received', data: req.body });
});
第三方中间件
首先通过 npm 安装,然后引入并使用。
javascript
// 1. 终端运行:npm install cookie-parser
const express = require('express');
const cookieParser = require('cookie-parser'); // 2. 引入
const app = express();
app.use(cookieParser()); // 3. 注册
app.get('/', (req, res) => {
// 现在可以读取请求中的 cookie 了
console.log('Cookies: ', req.cookies);
res.send('Hello World');
});
关键注意事项
执行顺序至关重要
中间件的执行顺序取决于它们在代码中注册(app.use)的顺序。请求会依次经过匹配的中间件。除了错误处理中间件,其他所有中间件(如解析中间件)必须在路由之前配置。
调用 next()
如果中间件函数不结束请求-响应周期(比如通过 res.send()),**必须调用 next()** 将控制权传递给下一个中间件,否则请求会被挂起。
错误处理中间件的位置
这是一个特例,必须放在所有其他 app.use()和路由调用之后,以确保能捕获到前面所有环节抛出的错误
Express路由与响应方法
| 类别 | 方法 | 主要用途 | 关键特点 |
|---|---|---|---|
| 路由定义 | app.METHOD(path, handler) |
处理特定HTTP方法(GET、POST等)的请求 | 精准匹配HTTP动词和路径 |
app.all(path, handler) |
处理所有HTTP方法对某路径的请求 | 适用于全局中间件或路径预处理 | |
app.route(path) |
为同一路径创建可链式调用的路由处理程序 | 避免路径重复书写,代码更清晰 | |
express.Router() |
创建模块化的路由处理程序 | 实现路由的模块化和管理 | |
| 响应方法 | res.send() |
发送多种类型数据(Buffer、String、Object、Array) | 自动设置Content-Type等响应头 |
res.json() |
发送JSON响应 | 自动设置Content-Type为application/json | |
res.end() |
快速结束响应,不发送数据 | 适用于无响应体的场景 | |
res.status() |
设置HTTP状态码 | 通常与其他响应方法链式调用 | |
res.redirect() |
重定向请求 | 可设置状态码(默认302) |
路径匹配示例
javascript
// 字符串路径:精确匹配
app.get('/about', (req, res) => {
res.send('About us');
});
// 字符串模式:使用特殊字符
app.get('/ab?cd', (req, res) => { // 匹配 acd 或 abcd
res.send('Pattern matched');
});
app.get('/ab+cd', (req, res) => { // 匹配 abcd, abbcd, abbbcd 等
res.send('Pattern matched with plus');
});
// 正则表达式:更灵活的匹配
app.get(/.*fly$/, (req, res) => { // 匹配 butterfly, dragonfly 等
res.send('Regex matched');
});
// 路由参数:捕获路径中的值
app.get('/users/:userId/books/:bookId', (req, res) => {
// 通过 req.params 访问捕获的值
res.send(`User ID: ${req.params.userId}, Book ID: ${req.params.bookId}`);
});
高级路由技巧
链式路由
使用
app.route()为同一路径定义多个方法
javascript
app.route('/book')
.get((req, res) => { res.send('Get a random book'); })
.post((req, res) => { res.send('Add a book'); })
.put((req, res) => { res.send('Update the book'); });
模块化路由
使用
express.Router()创建可挂载的模块化路由处理程序
javascript
// birds.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => { res.send('Birds home page'); });
router.get('/about', (req, res) => { res.send('About birds'); });
module.exports = router;
// 主文件 app.js
const birdsRouter = require('./birds');
app.use('/birds', birdsRouter); // 现在可以处理 /birds 和 /birds/about 的请求
响应方法详解
核心响应方法使用场景
res.send([body]):最通用的响应方法
javascript
// 发送字符串(Content-Type 自动设为 "text/html")
res.send('<p>Some HTML</p>');
// 发送对象或数组(Content-Type 自动设为 "application/json")
res.send({ user: 'tobi' });
res.send([1, 2, 3]);
// 发送 Buffer(Content-Type 自动设为 "application/octet-stream")
res.send(new Buffer('whoop'));
// 结合状态码
res.status(404).send('Sorry, we cannot find that!');
res.json([body]):专门发送JSON响应,会自动将非对象(如null、undefined)转换为JSON
javascript
res.json({ user: 'tobi' });
res.status(500).json({ error: 'message' });
res.json(null); // 有效,尽管null不是有效JSON
res.end([data][, encoding]) :快速结束响应,不发送数据。**除非需要无响应体结束,否则优先用 res.send()或 res.json()**
javascript
res.status(404).end(); // 结束响应,不发送数据
res.redirect([status,] path):重定向
javascript
res.redirect('/new-page'); // 默认302
res.redirect(301, '/permanent-new-page'); // 永久重定向
其他实用响应方法
res.status(code):设置HTTP状态码,常链式调用res.set(field [, value])或res.header(field [, value]):设置响应头res.type(type):设置 Content-Typeres.cookie(name, value [, options]):设置 Cookie
进阶应用与性能考量
中间件与路由结合
javascript
const logTime = (req, res, next) => {
console.log('Time:', Date.now());
next(); // 必须调用 next() 将控制权传递给下一个中间件或路由
};
app.use(logTime); // 应用级中间件,对所有路由生效
app.get('/specific', logTime, (req, res) => { // 路由级中间件
res.send('Specific route with logging');
});
错误处理
定义错误处理中间件时,函数参数为四个 (
err, req, res, next)
javascript
app.get('/problematic', (req, res, next) => {
try {
// 可能出错的操作
} catch (error) {
next(error); // 将错误传递给错误处理中间件
}
});
// 错误处理中间件(定义在路由之后)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
项目基础设计搭建优化
安装 express
pnpm i express -S
router\user.js
定义用户路由
javascript
const express = require('express');
const router = express.Router();
const userConroller = require('../controller/user');
router.get('/list', userConroller.list);
router.get('/:id', userConroller.getUserById);
module.exports = router;
controller\user.js
定义用户 controller
javascript
// 获取用户列表
const list = (req, res) => {
res.send('get user list');
};
// 获取指定id的用户
const getUserById = (req, res) => {
res.send(`get user ${req.params.id}`);
};
module.exports = {
list,
getUserById,
};
router\index.js
路由主入口,一次性导出所有路由
javascript
const express = require('express');
const router = express.Router();
router.use('/user', require('./user'));
router.use('/video', require('./video'));
module.exports = router;
app.js
应用主入口,进行一些全局配置,例如路由、日志、中间件等
javascript
const express = require('express');
const app = express();
// 设置相应数据类型
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 设置路由
app.use(require('./router'));
// 设置静态文件目录
app.use(express.static('public'));
// 监听3000端口
app.listen(3000, () => {
console.log('服务器已启动,监听端口3000');
});
数据处理模块-mongoose
安装 mongoose
pnpm i mongoose@6.3.1 -S
基本使用
model\userModel.js
javascript
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
phone: {
type: String,
required: true,
},
image: {
type: String,
default: null,
},
createdAt: {
type: Date,
default: Date.now(),
},
updateAt: {
type: Date,
default: Date.now(),
},
});
module.exports = userSchema;
model\index.js
使用 mongoose 连接数据库,导出所有数据库 Model
javascript
const mongoose = require('mongoose');
async function mian() {
await mongoose.connect('mongodb://localhost:27017/express-video');
}
mian()
.then((res) => {
console.log('mongo链接成功');
})
.catch((err) => {
console.log(err);
console.log('mongo链接失败');
});
module.exports = {
User: mongoose.model('User', require('./userModel')),
};
用户注册数据入库
router\user.js
用户注册路由
javascript
const express = require('express');
const router = express.Router();
const userConroller = require('../controller/user');
router.post('/register', userConroller.register);
module.exports = router;
controller\user.js
处理与用户路由相关的业务逻辑
javascript
const { User } = require('../model');
// 用户注册
const register = async (req, res) => {
// 创建用户 model 并存储
const user = new User(req.body);
try {
await user.save();
res.status(200).send('注册成功');
} catch (error) {
res.status(400).send(error.message);
}
};
module.exports = {
register,
};
使用 postman 测试用户注册功能


用户注册的密码加密问题
使用node内置的crypto库封装 md5 加密
util\md5.js
javascript
const crypto = require('crypto')
module.exports = str => {
return crypto.createHash('md5')
.update('by' + str)
.digest('hex')
}
model\userModel.js
使用封装好的 md5 加密
javascript
const md5 = require('../util/md5');
password: {
type: String,
required: true,
set: (val) => {
return md5(val);
},
}

客户端提交数据安全校验
安装 express-validator 库,用于提交参数校验
javascript
pnpm i express-validator@6.14 -S
middleware\validator\errorBack.js
封装统一错误请求处理函数
javascript
const { validationResult } = require('express-validator');
// 错误处理中间件
module.exports = (validator) => {
return async (req, res, next) => {
// 执行验证器
await Promise.all(validator.map((valid) => valid.run(req)));
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).send(errors.array()[0].msg);
}
next();
};
};
middleware\validator\userValidator.js
javascript
const { body } = require('express-validator');
const validate = require('./errorBack');
// 用户注册验证规则
module.exports.register = validate([
body('username')
.notEmpty()
.withMessage('用户名不能为空')
.bail()
.isLength({ min: 3 })
.withMessage('用户名长度最小为3')
.bail(),
body('password')
.notEmpty()
.withMessage('密码不能为空')
.bail()
.isLength({ min: 6, max: 16 })
.withMessage('密码长度最小为6,最大为16')
.bail(),
body('email')
.notEmpty()
.withMessage('邮箱不能为空')
.bail()
.isEmail()
.withMessage('邮箱格式不正确')
.bail(),
]);
router\user.js
在用户路由使用上述封装好的用户参数校验工具
javascript
const express = require('express');
const router = express.Router();
const userConroller = require('../controller/user');
const validator = require('../middleware/validator/userValidator');
router.post('/register', validator.register, userConroller.register);
module.exports = router;

数据唯一性验证
middleware\validator\userValidator.js
通过自定义校验规则验证邮箱和手机号是否已注册
javascript
body('email')
.notEmpty()
.withMessage('邮箱不能为空')
.bail()
.isEmail()
.withMessage('邮箱格式不正确')
.custom((value) => {
return User.findOne({ email: value }).then((user) => {
if (user) {
return Promise.reject('邮箱已被注册');
}
});
})
.bail(),
body('phone')
.notEmpty()
.withMessage('手机不能为空')
.bail()
.custom((value) => {
return User.findOne({ phone: value }).then((user) => {
if (user) {
return Promise.reject('手机号已被注册');
}
});
})
.bail()
用户登录信息对比
验证登录用户信息:邮箱是否已注册
middleware\validator\userValidator.js
javascript
// 用户登录验证规则
module.exports.login = validate([
body('email')
.notEmpty()
.withMessage('邮箱不能为空')
.bail()
.isEmail()
.withMessage('邮箱格式不正确')
.custom((value) => {
return User.findOne({ email: value }).then((user) => {
if (!user) {
return Promise.reject('邮箱未注册');
}
});
})
.bail(),
body('password')
.notEmpty()
.withMessage('密码不能为空')
.bail()
.isLength({ min: 6, max: 16 })
.withMessage('密码长度最小为6,最大为16')
.bail(),
]);
JWT用户身份认证
安装 jsonwebtoken
javascript
pnpm i jsonwebtoken - S
util\jwt.js
封装生成token以及校验token工具函数
javascript
const jwt = require('jsonwebtoken');
const { promisify } = require('util');
const { uuid } = require('../config/config.default');
// jwt.sign 转换为promise
const toJwt = promisify(jwt.sign);
// jwt.verify 转换为promise
const verifyJwt = promisify(jwt.verify);
// 生成 token
module.exports.createToken = async (user) => {
const token = await toJwt({ user }, uuid, {
expiresIn: 60 * 60,
});
return token;
};
// jwt 验证
module.exports.verifyToken = async (req, res, next) => {
//从 header 读取 token, 并解析 token
const authorization = req.headers.authorization;
const token = authorization ? authorization.split('Bearer ')[1] : null;
if (!token) {
return res.status(401).send('token 不存在');
}
try {
const user = await verifyJwt(token, uuid);
next();
} catch (error) {
res.status(401).send('token 验证失败');
}
};
controller\user.js
用户登录成功后生成token并返回
javascript
const { createToken } = require('../util/jwt');
const login = async (req, res) => {
try {
const dbBack = await User.findOne({
email: req.body.email,
password: req.body.password,
});
if (dbBack) {
// 将数据库返回的数据转换为json格式
const user = dbBack.toJSON();
// 生成 token
const token = await createToken(user);
user.token = token;
res.status(200).json(user);
} else {
res.status(400).json('账号密码错误');
}
} catch (error) {
res.status(400).json(error.message);
}
};
router\user.js
在其他需要登录状态下访问的接口添加校验token 中间件
javascript
const { verifyToken } = require('../util/jwt');
// 用户列表
router.get('/list', verifyToken, userConroller.list);

