当前位置: 首页 > news >正文

第7章 Node框架实战篇 - Express 中间件与RESTful API 接口规范

Express 不同中间件类别的使用方式
应用级中间件

通过 app.use([path], middleware)或 app.METHOD([path], middleware)注册。path是可选的,省略时对所有路径生效。

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实例。

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 会触发上述中间件和路由
错误处理中间件

必须放在所有路由和其他中间件之后

// 在路由中抛出错误(同步)
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 提供了几个开箱即用的中间件,最常见的是解析请求体和托管静态文件。

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 安装,然后引入并使用。

// 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) 

路径匹配示例
// 字符串路径:精确匹配
app.get('/about', (req, res) => {res.send('About us');
});// 字符串模式:使用特殊字符
app.get('/ab?cd', (req, res) => { // 匹配 acd 或 abcdres.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()为同一路径定义多个方法

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()创建可挂载的模块化路由处理程序

// 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]):最通用的响应方法 

// 发送字符串(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

res.json({ user: 'tobi' });
res.status(500).json({ error: 'message' });
res.json(null); // 有效,尽管null不是有效JSON

res.end([data][, encoding]):快速结束响应,不发送数据。除非需要无响应体结束,否则优先用 res.send()或 res.json()​ 

res.status(404).end(); // 结束响应,不发送数据

res.redirect([status,] path):重定向

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-Type
  • res.cookie(name, value [, options]):设置 Cookie
进阶应用与性能考量
中间件与路由结合
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)

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

定义用户路由

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

// 获取用户列表
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

路由主入口,一次性导出所有路由

const express = require('express');
const router = express.Router();router.use('/user', require('./user'));
router.use('/video', require('./video'));module.exports = router;

app.js

应用主入口,进行一些全局配置,例如路由、日志、中间件等

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

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

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

用户注册路由

const express = require('express');
const router = express.Router();
const userConroller = require('../controller/user');router.post('/register', userConroller.register); module.exports = router;

controller\user.js

处理与用户路由相关的业务逻辑

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

const crypto = require('crypto')
module.exports = str => {return crypto.createHash('md5').update('by' + str).digest('hex')
}

model\userModel.js

使用封装好的 md5 加密

const md5 = require('../util/md5');
password: {type: String,required: true,set: (val) => {return md5(val);},}

客户端提交数据安全校验

安装 express-validator 库,用于提交参数校验

pnpm i express-validator@6.14 -S

middleware\validator\errorBack.js

封装统一错误请求处理函数

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

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

在用户路由使用上述封装好的用户参数校验工具

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

通过自定义校验规则验证邮箱和手机号是否已注册

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

// 用户登录验证规则
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 

pnpm i jsonwebtoken - S

util\jwt.js

封装生成token以及校验token工具函数

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, 并解析 tokenconst 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并返回

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();// 生成 tokenconst 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 中间件

const { verifyToken } = require('../util/jwt');
// 用户列表
router.get('/list', verifyToken, userConroller.list);

http://www.dtcms.com/a/613572.html

相关文章:

  • 编译器用什么语言开发 | 深入探讨编译器开发的语言选择及其影响
  • 实战内网PTH上线域控
  • 基于YOLOv5-AUX的棕熊目标检测与识别系统实现
  • 东北网站建设国网典型设计最新版
  • 白酒网站设计广告设计与制作教程
  • 建设文明网站包括个人网站学生作业
  • 面对AI的思考,如何区分什么能力是人最根本的能力?
  • 当“能者”不再“多劳”:于倦怠深处,寻一方从容
  • 分布式系统测试包含子系统的系统测试,数据一致性测试,并发测试
  • less 工具 OpenHarmony PC适配实践
  • 人工智能之数据分析 numpy:第三章 Ndarray 对象和数组创建
  • Claude Code API Gateway 配置指南
  • 网站怎么做404 301深圳网站论坛建设
  • 网站后台m整套网站设计
  • claude code 食用指南
  • 专题1:双指针
  • 基于Vue的鲜花销售系统33n62(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
  • sdc 编写笔记
  • Rabbit MQ:概述
  • 建站之星管理中心注册海外公司
  • 【HarmonyOS】ArkWeb——从入门到入土
  • 微网站 微信网站优化服务是什么意思
  • VS Code 隐藏顶部标题栏中间的文字
  • 珠海网站哪家好如何给网站流量来源做标记通过在网址后边加问号?
  • Rust入门
  • Rust入门 之一
  • “伪”局域网
  • C语言编译软件Mac | 在Mac上选择最合适的C语言编译工具
  • 怎么样建设一个网上教学网站网页版微信二维码不能直接识别
  • Linux BPF 技术深度解析:从原理到实践