第五部分:第五节 - Express 路由与中间件进阶:厨房的分工与异常处理
随着你的 Express 应用变得越来越大,所有的路由和中间件都写在一个文件里会变得难以管理。这时候就需要将代码进行拆分和组织。此外,一个健壮的后端应用必须能够优雅地处理错误和一些常见的 Web 开发问题,比如跨域。
路由模块化 (express.Router
):
express.Router
是 Express 提供的一个迷你应用,它有自己的中间件和路由。你可以把处理某个特定资源的路由(比如所有用户相关的路由)放到一个单独的文件中,然后像中间件一样将其挂载到主应用上。这就像在厨房里为不同的菜品(资源)设立了专门的操作台(路由文件),每个操作台有自己的工作流程,但它们最终都属于整个厨房(主应用)。
创建一个新的文件,比如 routes/users.js
:
// routes/users.js
const express = require('express');
const router = express.Router(); // 创建一个 Router 实例// 这里可以定义用户相关的中间件,只应用于本路由文件中的路由
// router.use((req, res, next) => { console.log('用户路由中间件'); next(); });// 定义用户相关的路由
router.get('/', (req, res) => {// 处理获取所有用户的逻辑res.send('获取所有用户');
});router.get('/:userId', (req, res) => {const userId = req.params.userId;// 处理获取单个用户的逻辑res.send(`获取用户 ID: ${userId}`);
});router.post('/', (req, res) => {// 处理创建用户的逻辑res.send('创建新用户');
});// ... 其他用户相关的路由 (PUT, DELETE)module.exports = router; // 导出 router 实例 (CommonJS 方式)// 如果使用 ES Modules:
// export default router;
在你的主应用文件 app.js
中导入并使用这个路由模块:
// app.js
const express = require('express');
const app = express();
const port = 3000;// 导入用户路由模块
const usersRouter = require('./routes/users');
// const usersRouter from './routes/users.js'; // ES Modules// ... 其他中间件 (如 body-parser)// 将用户路由模块挂载到 /api/users 路径下
app.use('/api/users', usersRouter);// ... 其他路由和中间件// 启动服务器
app.listen(port, () => {console.log(`应用运行在 http://localhost:${port}`);
});// 现在访问 /api/users 会由 usersRouter 处理
// 访问 /api/users/123 会由 usersRouter.get('/:userId', ...) 处理
错误处理中间件:
在 Express 中,错误处理中间件有四个参数:(err, req, res, next)
。当你在任何路由或普通中间件中调用 next(err)
并传入一个错误对象时,Express 会跳过后续的路由和中间件,直接进入错误处理中间件。这就像厨房里某个环节出了问题(比如菜烧焦了),不用继续整个流程,直接通知服务员(调用错误处理中间件)去处理这个“异常订单”。
通常,你会在所有路由和普通中间件的后面定义错误处理中间件,以捕获所有未被处理的错误。
// app.js (接着上面的代码)// ... 所有路由和普通中间件 ...// 404 错误处理 (放在所有有效路由之后)
app.use((req, res, next) => {res.status(404).send("对不起,找不到该页面!");
});// 错误处理中间件 (四个参数)
app.use((err, req, res, next) => {console.error("服务器端错误:", err.stack); // 打印错误堆栈到服务器控制台res.status(500).send("服务器内部出错!"); // 向客户端发送 500 状态码和错误信息
});// 在路由中模拟错误
app.get('/error-test', (req, res, next) => {// 模拟一个错误const myError = new Error("这是一个测试错误");next(myError); // 将错误传递给错误处理中间件
});
常用的第三方中间件:
Express 的生态系统非常丰富,有很多优秀的第三方中间件可以方便地集成。
morgan
(日志记录): 记录所有收到的 HTTP 请求信息,对于调试和监控非常有帮助。就像餐厅里的订单记录系统。- 安装:
npm install morgan
- 使用:
const morgan = require('morgan'); app.use(morgan('dev'));
(dev 是预设的日志格式)
- 安装:
cors
(跨域资源共享): 处理浏览器的同源策略限制,允许来自不同域的前端应用访问你的后端 API。这就像允许来自不同地区的顾客在你的餐厅点餐。- 安装:
npm install cors
- 使用:
const cors = require('cors'); app.use(cors());
(允许所有跨域请求,也可以配置更精细的规则)
- 安装:
// app.js (接着上面的代码)const morgan = require('morgan');
const cors = require('cors');// 使用 morgan 中间件记录日志
app.use(morgan('dev'));// 使用 cors 中间件处理跨域
app.use(cors());// ... 其他路由和中间件 ...
小例子:模块化路由和全局错误处理
请参考上面关于 express.Router
和错误处理中间件的代码示例,将它们集成到一个完整的 Express 应用中。
小结: 使用 express.Router
可以将复杂的路由逻辑拆分到单独的文件中,提高代码的可组织性。错误处理中间件是捕获和处理应用错误的统一方式。morgan
和 cors
是两个非常常用的第三方中间件,分别用于日志记录和跨域处理。
练习:
- 在你的 Express 项目中,为之前的“书籍”资源创建一个单独的路由文件 (
routes/books.js
)。 - 在
routes/books.js
中,使用express.Router()
定义至少两个路由(例如 GET / 和 GET /:bookId)。 - 在主应用文件
app.js
中导入routes/books.js
并将其挂载到/api/books
路径下。 - 在主应用中添加一个 404 错误处理中间件(放在所有有效路由之后)。
- 在主应用中添加一个全局错误处理中间件,当接收到错误时,记录错误到控制台并向客户端发送 500 状态码。
- 安装并使用
morgan
中间件记录所有请求日志。 - 安装并使用
cors
中间件,允许所有来源的跨域请求。
相关文章:
第五部分:第五节 - Express 路由与中间件进阶:厨房的分工与异常处理
随着你的 Express 应用变得越来越大,所有的路由和中间件都写在一个文件里会变得难以管理。这时候就需要将代码进行拆分和组织。此外,一个健壮的后端应用必须能够优雅地处理错误和一些常见的 Web 开发问题,比如跨域。 路由模块化 (express.Ro…...
在 CentOS 7.9 上部署 node_exporter 并接入 Prometheus + Grafana 实现主机监控
文章目录 在 CentOS 7.9 上部署 node_exporter 并接入 Prometheus Grafana 实现主机监控环境说明node_exporter 安装与配置下载并解压 node_exporter创建 Systemd 启动服务验证服务状态验证端口监听 Prometheus 配置 node_exporter 监控项修改 prometheus.yml重新加载 Prometh…...

C++--内存管理
内存管理 1. C/C内存分布 在C语言阶段,常说局部变量存储在栈区,动态内存中的数据存储在堆区,静态变量存储在静态区(数据段),常量存储在常量区(代码段),其实这里所说的栈…...
Java实现PDF加水印功能:技术解析与实践指南
Java实现PDF加水印功能:技术解析与实践指南 在当今数字化办公环境中,PDF文件因其跨平台兼容性和格式稳定性而被广泛应用。然而,为了保护文档的版权、标记文档状态(如“草稿”“机密”等)或增加文档的可追溯性…...
Django + Celery 打造企业级大模型异步任务管理平台 —— 从需求到完整实践(含全模板源码)
如需完整工程文件(含所有模板),可回复获取详细模板代码。 面向人群:自动化测试工程师、企业中后台开发人员、希望提升效率的 AI 业务从业者 核心收获:掌握 Django 三表关系设计、Celery 异步任务实践、基础 Web 交互与前后端分离思路,源码可直接落地,方便二次扩展 一、系…...

TC3xx学习笔记-UCB BMHD使用详解(二)
文章目录 前言Confirmation的定义Dual UCB: Confirmation StatesDual UCB: Errored State or ECC Error in the UCB Confirmation CodesECC Error in the UCB ContentDual Password UCB ORIG and COPY Re-programming UCB_BMHDx_ORIG and UCB_BMHDx_COPY (x 0-3)BMHD Protecti…...

用Python实现数据库数据自动化导出PDF报告:从MySQL到个性化文档的全流程实践
本文将介绍如何使用Python构建一个自动化工具,实现从MySQL数据库提取员工数据,并为每位员工生成包含定制化表格的PDF报告。通过该方案,可显著提升数据导出效率,避免手动操作误差,同时支持灵活的格式定制。 需求&#…...

实战设计模式之状态模式
概述 作为一种行为设计模式,状态模式允许对象在其内部状态改变时,改变其行为。这种模式通过将状态逻辑从对象中分离出来,并封装到独立的状态类中来实现。每个状态类代表一种特定的状态,拥有自己的一套行为方法。当对象的状态发生变…...
人工智能、机器学习与深度学习:概念解析与内在联系
人工智能、机器学习与深度学习:概念解析与内在联系 一、人工智能(Artificial Intelligence, AI) (一)人工智能的定义 人工智能的定义随着技术发展不断演变。从广义上讲,人工智能是指通过计算机技术实现的…...

什么是着色器 Shader
本人就是图形学结课了,对 OpenGL着色器还有很多疑问嘿嘿 文章目录 为什么要有着色器vshaderfshader 本文围绕 vshader 和 fshader 代码示例讲解。 (着色器代码取自本人简单OpenGL项目 https://github.com/DBWGLX/-OpenGL-3D-Lighting-and-Shadow-Modeli…...

Redis的主从架构
主从模式 全量同步 首先主从同步过程第一步 会先比较replication id 判断是否是第一次同步假设为第一次同步 那么就会 启动bgsave异步生成RDB 同时fork子进程记录生成期间的新数据发送RDB给从节点 清空本地数据写入RDB 增量同步 对比ReplicationID不同因此选择增量同步在Rep…...

博客系统功能测试
博客系统网址:http://8.137.19.140:9090/blog_list.html 主要测试内容 功能测试、界面测试、性能测试、易用性测试、安全测试、兼容性测试、弱网测试、安装卸载测试、压力测试… 测试方法及目的 利用selenium和python编写测试脚本,对博客系统进行的相关…...

【深度学习新浪潮】什么是多模态大模型?
多模态大模型是人工智能领域的前沿技术方向,它融合了多种数据模态(如文本、图像、语音、视频、传感器数据等),并通过大规模参数模型实现跨模态的联合理解与生成。简单来说,这类模型就像人类一样,能同时“看”“听”“读”“说”,并将不同信息关联起来,完成复杂任务。 …...

机器学习前言2
1.机器学习 2.机器学习模型 3.模型评价方法 4.如何选择合适的模型 介绍 机器学习(Machine Learning, ML)是人工智能(AI)的核心分支,致力于通过数据和算法让计算机系统自动“学习”并改进性能,而无需显式编…...

【成品设计】基于Arduino的自动化农业灌溉系统
《基于STM32的单相瞬时值反馈逆变器》 硬件设计: ESP-C3最小系统板:主控芯片,内部集成wifi。土壤湿度传感器:采集土壤湿度。温度传感器:采集土壤温度。水泵模块:水泵继电器软管。按键3个:参数…...

前端页面 JavaScript数据交互
前言:学习JavaScript为前端设置动态效果,实现交互。JavaScript是一种广泛应用于网页开发的脚本语言,它能够使网页变得更加动态和交互性。作为一种客户端脚本语言,JavaScript可以被嵌入到HTML中,并且可以被所有现代的网…...

esp32课设记录(三)mqtt通信记录 附mqtt介绍
目录 安装mqttx(云端部署) 安装mosquitto(本地部署) 编程,连接wifi 编程,连接mqtt,实现数据接收 实际效果展示: 附录:mqtt介绍 工作流程简述: 工作流…...

string类(详解)
【本节目标】 1. 为什么要学习string类 2. 标准库中的string类 3. string类的模拟实现 4. 扩展阅读 1. 为什么学习string类? 1.1 C语言中的字符串 C 语言中,字符串是以 \0 结尾的一些字符的集合,为了操作方便, C 标准库中提供…...

MATLAB | R2025a 更新了哪些有趣的东西?
千呼万唤始出来,MATLAB R2025A 来见面,这次更新比往常晚了两个月,让我们看看更了哪些好玩的新东西叭:首先下载更新启动一气呵成,映入眼帘的是: 1 基本界面 基本界面变得和 MATLAB 网页版一模一样了&#…...

前缀和——和为K的子数组
作者感觉本题稍稍有点难度,看了题解也思考了有一会TWT 显然,暴力我们是不可取的,但这里我们可以采取一种新的遍历数组形式,从后向前,也就是以i位置为结尾的所有子数组,这个子数组只统计i位置之前的。 然后…...
React 第四十二节 Router 中useLoaderData的用途详解
一、前言 useLoaderData,用于在组件中获取路由预加载的数据。它通常与路由配置中的 loader 函数配合使用,用于在页面渲染前异步获取数据(如 API 请求),并将数据直接注入组件,从而简化数据流管理。 二、us…...
千问大模型部署
参考链接:https://zhuanlan.zhihu.com/p/19698254692 tips:部署比较简单,除去上述教程中的步骤外,该文章主要是想记录过程中遇到的问题。因为我是双平台(arm/x86)部署,x86在python3.xx版本成功…...

深入理解 ZAB:ZooKeeper 原子广播协议的工作原理
目录 ZAB 协议:ZooKeeper 如何做到高可用和强一致?🔒ZAB 协议的核心目标 🎯ZAB 协议的关键概念 💡ZAB 协议的运行阶段 🎬阶段一:Leader 选举 (Leader Election) 🗳️阶段二ÿ…...
GO语言语法---if语句
文章目录 1. 基本语法1.1 单分支1.2 双分支1.3 多分支 2. Go特有的if语句特性2.1 条件前可以包含初始化语句2.2 条件表达式不需要括号2.3 必须使用大括号2.4 判断语句所在行数控制 Go语言的if语句用于条件判断,与其他C风格语言类似,但有一些独特的语法特…...
Unix Bourne Shell
本文来源 : 腾讯元宝 Unix Bourne Shell(简称sh)是Unix系统中最经典的命令行解释器(shell),由Stephen Bourne于1977年在贝尔实验室开发,并成为后续众多shell(如bash、ksh等ÿ…...

GraphPad Prism项目的管理
《2025新书现货 GraphPad Prism图表可视化与统计数据分析(视频教学版)雍杨 康巧昆 清华大学出版社教材书籍 9787302686460 GraphPadPrism图表可视化 无规格》【摘要 书评 试读】- 京东图书 GraphPad Prism统计数据分析_夏天又到了的博客-CSDN博客 项目…...

驱动-Linux定时-timer_list
了解内核定时相关基础知识 文章目录 简要介绍timer_list 特点API 函数实验测试程序 - timer_mod.c编译文件-Makefile实验验证 注意事项总结 简要介绍 硬件为内核提供了一个系统定时器来计算流逝的时间(即基于未来时间点的计时方式, 以当前时刻为计时开始…...

STM32F103_LL库+寄存器学习笔记22 - 基础定时器TIM实现1ms周期回调
导言 如上所示,STM32F103有两个基本定时器TIM6与TIM7,所谓「基本定时器」,即功能最简单的定时器。 项目地址: github: LL库: https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_ll_library22_Basic_Timer寄存器方…...

5个yyds的.Net商城开源项目
今天一起来盘点下5个商城开源项目。 1、支持多语言、多商店的商城,.Net7 EF7领域驱动设计架构(Smartstore) 项目简介 Smartstore 支持桌面和移动平台、多语言、多商店、多货币的商城,并支持SEO优化,支持无限数量的…...
C++:与7无关的数
【描述】 一个正整数,如果它能被7整除,或者它的十进制表示法中某一位上的数字为7,则称其为与7相关的数.现求所有小于等于n(n < 100)的与7无关的正整数的平方和. 【输入】 输入为一行,正整数n(n < 100) 【输出】 输出一行,包含一个整数,即小于等于n…...