node.js基础学习-express框架-路由及中间件(十)
一、前言
Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建,提供了一系列用于构建 Web 应用程序和 API 的功能,使开发者能够更高效地处理 HTTP 请求和响应,专注于业务逻辑的实现。
其特点包括简单易用、中间件机制丰富、路由系统灵活等。通过使用 Express,可以快速搭建服务器,处理不同类型的请求,如网页渲染、数据接口提供等多种功能。
二、路由
1. 基本的路由格式
一个基本的 Express 路由由 HTTP 方法(如app.get、app.post等)、路径(如'/'、'/about'等)和一个或多个回调函数组成。例如:
app.get('/', (req, res) => {res.send('Hello World!');
});
这里app.get表示处理 HTTP GET 请求,'/'是请求路径,(req, res) => {... }是回调函数。req代表请求对象,包含了请求的相关信息,如请求头、请求参数等;res代表响应对象,用于发送响应给客户端,如res.send方法用于发送响应内容。
2. 路径匹配
-
静态路径匹配:如
app.get('/about', (req, res) => {... });,只有当请求路径完全匹配/about时,这个路由才会被触发。 -
动态路径匹配:可以使用参数来创建动态路由。例如,
app.get('/users/:id', (req, res) => { const id = req.params.id; res.send(User ${id}); });,:id是一个动态参数,当请求路径类似/users/1、/users/2等时,req.params.id可以获取到相应的参数值,用于根据不同的用户 ID 等动态信息返回不同的响应。 -
路径参数的多种匹配方式
-
可选参数:在路径中使用
?来表示可选部分。例如app.get('ab?cd', (req, res) => {... });可以匹配ab或者abcd这样的路径。 -
通配符匹配:使用
*来表示匹配任意字符序列。例如app.get('ab*cd', (req, res) => {... });可以匹配ab后面跟着任意字符再接着cd的路径,如abxyzcd等。 -
正则表达式匹配:可以直接在路径中使用正则表达式。例如
app.get('/ab+cd', (req, res) => {... });匹配ab后面至少有一个字符再接着cd的路径,符合正则表达式ab+cd的模式。
3. 路由方法(HTTP方法)
Express 支持多种 HTTP 方法来定义路由,常见的有:
- GET 请求:用于从服务器获取数据。例如,获取网页内容、查询用户信息等场景。如
app.get('/books', (req, res) => { // 查询书籍信息并返回 });。 - POST 请求:通常用于向服务器提交数据,如提交表单数据、上传文件等。例如,
app.post('/login', (req, res) => { // 处理用户登录信息提交 });。 - PUT 请求:用于更新服务器上的数据。例如,更新用户信息、修改文章内容等场景。
app.put('/users/:id', (req, res) => { // 根据用户ID更新用户信息 });。 - DELETE 请求:用于删除服务器上的数据。比如删除用户记录、删除文件等。
app.delete('/products/:id', (req, res) => { // 根据产品ID删除产品信息 });。
三、 Express 中间件
中间件是在请求和响应周期中被调用的函数,它可以访问请求对象(req)、响应对象(res)和应用程序的请求 - 响应循环中的下一个中间件(next)。中间件可以执行各种任务,如日志记录、身份验证、数据预处理等,然后可以选择将请求传递给下一个中间件或者路由处理函数。
1. 中间件的使用方式
单个中间件:在路由处理函数中,可以有一个或多个中间件。例如:
app.get('/home', (req, res, next) => {console.log('This is a middleware');next();
}, (req, res) => {res.send('This is the home page');
});
这里第一个函数是中间件,它先打印一条日志,然后调用next()将控制权传递给下一个函数(这里是路由处理函数),用于发送响应。如果不调用next()函数,下一个中间件将不会被执行。
中间件数组:也可以将多个中间件组合成一个数组来使用。例如:
const func1 = (req, res, next) => {console.log('This is a middleware 1');next();
};
const func2 = (req, res, next) => {console.log('This is a middleware 2');next();
};
const func3 = (req, res, next) => {console.log('This is a middleware 3');next();
};
app.get('/list', [func1, func2, func3], (req, res) => {res.send('This is the list page');
});
当请求/list路径时,会依次执行func1、func2、func3这三个中间件,最后执行路由处理函数来发送响应。
2. 中间件之间的传值
中间件可以通过req或res对象在中间件之间传递值。例如:
const func4 = (req, res, next) => {req.name = 'John';res.age = 33;next();
};
const func5 = (req, res, next) => {const name = req.name;const age = res.age;res.send(`<h1>Hello ${name}, you are ${age} years old!</h1>`);
};
app.get('/hello', [func4, func5], (req, res) => {
});
在func4中间件中,通过req.name和res.age设置了值,然后在func5中间件中可以获取这些值来生成响应。
3. 不同类型的中间件
3.1 应用级中间件
通过app.use()方法来添加应用级中间件,它可以应用于整个应用程序或者特定的路径。如果没有指定路径,中间件会应用于所有的请求路径。例如:
const express = require('express');
const app = express();
app.use((req, res, next) => {console.log('This middleware is called for every request');next();
});
app.get('/hello', (req, res) => {res.send('Hello World');
});
app.listen(3000, () => {console.log('Server is running on port 3000');
});
在这个例子中,定义的中间件会在每个请求到达服务器时被调用,它先打印一条日志,然后通过next()函数将请求传递给下一个中间件或者路由处理函数。
3.2 特定路径的应用级中间件
可以指定中间件应用的路径,这样中间件只会对匹配该路径及其子路径的请求起作用。例如:
app.use('/admin', (req, res, next) => {console.log('This middleware is for /admin and its sub - paths');// 可以在这里进行权限验证等操作next();
});
app.get('/admin/dashboard', (req, res) => {res.send('Admin Dashboard');
});
当请求/admin路径或者以/admin开头的子路径(如/admin/dashboard)时,中间件会被调用。这对于对特定模块或功能进行统一的预处理(如权限验证)非常有用。
3.3 路由中间件
路由中间件和应用级中间件类似,也是通过app.use()在特定路由路径下注册,在路由处理函数之前执行,用于对该路由的请求进行预处理等操作。例如:
app.use('/api/users', (req, res, next) => {console.log('This is a route - level middleware for /api/users');// 可以在这里进行用户相关的预处理,如验证用户是否存在等next();
});
app.get('/api/users', (req, res) => {res.send('List of users');
});
这里的路由中间件会在处理/api/users路由的请求之前被调用,用于对用户相关的请求进行预处理。
3.4 错误处理中间件
错误处理中间件用于捕获和处理在路由处理函数或其他中间件中抛出的错误。它的函数签名与普通中间件略有不同,有四个参数(err, req, res, next),其中err参数用于接收错误对象。错误处理中间件应该放在所有其他中间件和路由定义之后,这样才能捕获它们抛出的错误。例如:
app.use((err, req, res, next) => {console.error(err.stack);res.status(500).send('Something went wrong!');
});
app.get('/error - route', (req, res, next) => {const error = new Error('This is a test error');next(error);
});
在这个例子中,当请求/error - route时,会在路由处理函数中创建一个错误对象并通过next(error)将错误传递给错误处理中间件。错误处理中间件会在控制台打印错误栈信息,然后向客户端返回一个状态码为500(服务器内部错误)的响应,消息为Something went wrong!。
3.5 内置中间件
**express.json():**用于解析application/json格式的请求体。在处理 POST 或 PUT 请求,且请求体数据为 JSON 格式时非常有用。例如:
app.use(express.json());
app.post('/data', (req, res) => {const data = req.body;// 处理接收到的JSON数据res.send('Data received');
});
当客户端发送一个application/json格式的 POST 请求到/data路径时,express.json()中间件会自动将请求体中的 JSON 数据解析为 JavaScript 对象,并挂载到req.body上,方便在路由处理函数中使用。
**express.urlencoded({ extended: false }):**用于解析application/x -www-form-urlencoded格式的请求体。通常用于处理 HTML 表单提交的数据。例如:
app.use(express.urlencoded({ extended: false }));
app.post('/form-data', (req, res) => {const formData = req.body;// 处理接收到的表单数据res.send('Form data received');
});
username=jun&password=123456这种格式的数据解析
3.6 第三方中间件
**morgan:**用于日志记录,它可以记录每个请求的详细信息,如请求方法、请求路径、响应状态码等。例如:
const morgan = require('morgan');
app.use(morgan('combined'));
这里morgan('combined')是一种日志格式选项,它会记录详细的请求信息。morgan还有其他日志格式,如'dev'(适合开发环境)、'common'等,开发者可以根据实际需求选择。
**cors:**用于解决跨域资源共享问题。例如:
const cors = require('cors');
app.use(cors({origin: 'http://example.com',methods: ['GET', 'POST'],
}));
这个配置允许来自http://example.com的请求使用GET和POST方法进行跨域访问。可以根据具体的业务场景调整origin(允许的源)、methods(允许的请求方法)等参数来满足跨域需求。
四、 模块化的路由中间件
express.Router是 Express 框架中的一个重要组件,它提供了一种模块化的方式来定义路由。使用Router可以将路由分组并封装到独立的模块中,这有助于组织大型应用程序的路由结构,使代码更加清晰、易于维护和扩展。
1. 创建和使用express.Router实例
创建实例:首先,需要创建一个Router实例。例如:
const express = require('express');
const router = express.Router();
定义路由:在Router实例上可以像在主app对象上一样定义各种 HTTP 方法的路由。例如:
router.get('/', (req, res) => {res.send('This is the root of the sub - router');
});
router.post('/data', (req, res) => {const data = req.body;res.send(`Received data: ${data}`);
});
挂载到主应用:创建并定义好Router的路由后,需要将其挂载到主 Express 应用上。例如:
const app = express();
app.use('/api', router);
这里将router挂载到/api路径下,这意味着router中定义的所有路由实际上是相对于/api路径的。例如,router中的'/'路由实际上对应的是/api/路径,'/data'路由对应的是/api/data路径。
2. 路由模块化的优势
- 代码结构清晰:通过将相关的路由分组到不同的
Router模块中,可以将一个大型应用的路由按照功能模块(如用户管理、产品管理、订单管理等)进行划分。例如,在一个电商应用中,可以有一个userRouter用于处理用户相关的路由(注册、登录、获取用户信息等),一个productRouter用于处理产品相关的路由(产品列表、产品详情、添加产品等),这样的代码结构更易于理解和维护。 - 复用性增强:
Router模块可以在不同的应用或者应用的不同部分复用。比如,一个通用的authRouter用于处理身份验证相关的路由(登录、验证 token 等),可以在多个不同的微服务或者应用模块中使用,只要它们遵循相同的接口和认证机制。 - 团队协作便利:在团队开发中,不同的开发人员可以负责不同的
Router模块,这样可以并行开发,减少代码冲突。例如,前端开发人员和后端开发人员可以分别开发与用户界面交互相关的路由和与数据库操作相关的路由,通过定义好的接口(如 API 路由)进行协作。
3. 中间件在express.Router中的使用
在Router级别使用中间件:可以在Router实例上使用中间件,这些中间件会应用到该Router所定义的所有路由上。例如:
const loggerMiddleware = (req, res, next) => {console.log(`Received a request for ${req.url}`);next();
};
router.use(loggerMiddleware);
这里定义了一个日志记录中间件loggerMiddleware,并通过router.use()将其应用到router上。这样,router中所有的路由在被访问时,都会先执行这个日志记录中间件。
在特定路由中使用中间件:也可以在Router的特定路由中使用中间件。例如:
const authMiddleware = (req, res, next) => {const token = req.headers.authorization;if (!token) {return res.status(401).send('Unauthorized: No token provided');}// 验证token的其他逻辑next();
};
router.get('/protected - route', authMiddleware, (req, res) => {res.send('This is a protected route');
});
在这个例子中,authMiddleware中间件只应用于/protected - route这个特定的路由。当访问该路由时,会先执行中间件进行身份验证,只有验证通过后才会执行路由处理函数。
4. 嵌套express.Router实例
express.Router实例可以进行嵌套,以创建更复杂的路由层次结构。这在构建具有多层级关系的 API 或者应用程序时非常有用。例如,在一个具有用户组和用户的应用中,可以先有一个groupRouter用于处理用户组相关的路由,在groupRouter内部再嵌套一个userRouter用于处理每个用户组内用户相关的路由。
const groupRouter = express.Router();
const userRouter = express.Router();
// 定义用户组相关的路由
groupRouter.get('/', (req, res) => {res.send('List of groups');
});
groupRouter.post('/', (req, res) => {res.send('Create a new group');
});
// 在用户组路由中嵌套用户路由
userRouter.get('/', (req, res) => {res.send('List of users in the group');
});
userRouter.post('/', (req, res) => {res.send('Add a new user to the group');
});
groupRouter.use('/:groupId/users', userRouter);
const app = express();
app.use('/groups', groupRouter);
在这个例子中,userRouter被嵌套在groupRouter内部。groupRouter处理用户组的基本路由,如获取用户组列表和创建新用户组。userRouter处理用户组内用户的相关路由,如获取用户组内用户列表和添加新用户到用户组。通过groupRouter.use('/:groupId/users', userRouter)将userRouter挂载到groupRouter的/:groupId/users路径下,这样就创建了一个嵌套的路由结构。当请求/groups/1/users(假设1是用户组 ID)时,会先由groupRouter处理/groups/1部分的路由,然后将请求传递给userRouter处理/users部分的路由。
相关文章:
node.js基础学习-express框架-路由及中间件(十)
一、前言 Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建,提供了一系列用于构建 Web 应用程序和 API 的功能,使开发者能够更高效地处理 HTTP 请求和响应,专注于业务逻辑的实现。 其特点包括简单易用…...
使用MSYS搭建linux开发环境踩坑笔记
前言: 使用linux系统或虚拟机进行嵌入式linux开发是常规方法; 使用MSYS是用于尝鲜和研究。 由于windows和linux的差异,使用MSYS代替Linux虚拟机会遇到很多坑。 主要原因在于: 1. windows和linux文件系统的差异:win不…...
vue3+ts+vite+ElementPlus上传进度条实时更新(UPLoad和progress)。
需求: 上传文件时,展示进度条实时更新: 下面是代码片段: <!-- 添加媒体弹窗 -- 上传 --><el-dialog v-model"centerDialogVisible" title"媒体信息" width"700" :close-on-click-modal"false&qu…...
AspNet WebAPI 模型绑定问题
继承System.Web.Http.ApiController的Action的Model如果被[Serializable]定义,会导致Model的字段无法绑定。 Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll [Serializable] public class Model {public string id { get; set; } }public MyA…...
Android 图形系统之七:SurfaceFlinger
一. 引言 什么是 SurfaceFlinger?SurfaceFlinger 的核心作用和地位?为什么需要了解 SurfaceFlinger? 二. SurfaceFlinger 的基本概念 Surface 和 SurfaceFlinger 的关系SurfaceFlinger 与图形渲染(OpenGL ES 和 Vulkan…...
14、鸿蒙学习——管理通知角标
针对未读的通知,系统提供了角标设置接口,将未读通知个数显示在桌面图标的右上角角标上。 通知增加时,角标上显示的未读通知个数需要增加。 通知被查看后,角标上显示的未读通知个数需要减少,没有未读通知时࿰…...
TongRDS分布式内存数据缓存中间件
命令 优势 支持高达10亿级的数据缓冲,内存优化管理,避免GC性能劣化。 高并发系统设计,可充分利用多CPU资源实现并行处理。 数据采用key-value多索引方式存储,字段类型和长度可配置。 支持多台服务并行运行,服务之间可互…...
[在线实验]-RabbitMQ镜像的下载与部署
镜像下载 docker的rabbitmq镜像资源-CSDN文库 加载镜像 docker load --input rabbitmq.tar 给镜像打标签 这里发现镜像名为none,需要给镜像重命名下 docker tag [镜像id] [新镜像名称]:[新镜像标签] docker tag ebaf409ffbe2 rabbitmq:management 运行镜像…...
Linux 系统文件描述符(File Descriptor)小白级介绍
1. 概述 Linux 遵循"一切皆文件"的理念。在 Linux 系统中,文件描述符是一个索引值(非负整数),指向内核为每个进程所维护的该进程打开文件的记录表。 如上所述,每个进程都维护着一张文件描述符表。 文件描述…...
【Verilog】实验二 数据选择器的设计与vivado集成开发环境
目录 一、实验目的 二、实验环境 三、实验任务 四、实验原理 五、实验步骤 top.v mux2_1.v 一、实验目的 1. 掌握数据选择器的工作原理和逻辑功能。 2. 熟悉vivado集成开发环境。 3. 熟悉vivado中进行开发设计的流程。 二、实验环境 1. 装有vivado的计算机。 2. Sw…...
IDL学习笔记(三)OMI数据处理。hdf5文件读取,图像反转,GeoTiff区别,月季年均值计算提取输出,单位转换,运行时间计算
modis Level 2 grid 数据是全球格网化数据。一天的数据全在其中。 modis Level 1 和 2 数据是一景一景的影像。 IDL学习笔记(三)OMI数据处理 hdf5文件读取单位转换,输出hdf5数据集的图像,并检查图像经纬度是否正确,若错…...
深入浅出:PHP中的数据类型全解析
文章目录 引言理解数据类型标量类型整数 (integer)浮点数 (float)布尔值 (boolean)字符串 (string) 复合类型数组 (array)对象 (object)资源 (resource)NULL 特殊类型Callable强制类型转换 实战案例总结与展望参考资料 引言 在编程的世界里,数据类型是构建任何应用…...
要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出
要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出 要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出方案一解决方案(openresty使用cjson)说明:使…...
智慧油客:从初识、再识OceanBase,到全栈上线
今天,我们邀请了智慧油客的研发总监黄普友,为我们讲述智慧油客与 OceanBase 初识、熟悉和结缘的故事。 智慧油客自2016年诞生以来,秉持新零售的思维,成功从过去二十年间以“以销售产品为中心”的传统思维模式,转向“以…...
ClickHouse守护进程
背景描述 维护CK过程中,有时候会有CK OOM,并且CK自己没有自动拉起的情况出现;那么这个时候就需要守护进程,最初我不说了Supervisor来做守护进程,但是当我手动kill的时候发现并没有自动拉起。 解决方案 于是乎自己写…...
智能合约
06-智能合约 0 啥是智能合约? 定义 智能合约,又称加密合约,在一定条件下可直接控制数字货币或资产在各方之间转移的一种计算机程序。 角色 区块链网络可视为一个分布式存储服务,因为它存储了所有交易和智能合约的状态 智能合约还…...
SQL面试题——拼多多SQL面试题 求连续段的起始位置和结束位置
拼多多SQL面试题 求连续段的起始位置和结束位置 今天的题目来自拼多多,我们先看一下题目描述 有一张表ids记录了id,id不重复,但是会存在间断,求出连续段的开始位置和结束位置 +---+ | id| +---+ | 1| | 2| | 3| | 5| | 6| | 8| | 10| | 12| | 13| | 14| | 15| +--…...
玩《三角洲行动》遇到游戏运行故障是什么原因?游戏运行故障要怎么解决?预防游戏运行故障问题出现
《三角洲行动》游戏运行故障解析与解决方案:原因、解决与预防 在畅游《三角洲行动》这款充满挑战与激情的游戏时,玩家可能会遭遇各种游戏运行故障,如卡顿、闪退、无法启动等问题。我将结合自己丰富的经验和知识,为大家深入剖析《…...
基于灰色神经网络的订单需求预测
灰色神经网络(Grey Neural Network, GNN) 是将灰色系统理论与人工神经网络相结合的一种模型,旨在处理不完全信息和小样本问题。灰色神经网络利用灰色系统的预测优势和神经网络的学习能力,能够在信息不完整或数据不充分的情况下实现…...
记录学习《手动学习深度学习》这本书的笔记(三)
这两天看完了第六章:卷积神经网络,巧的是最近上的专业选修课刚讲完卷积神经网络,什么卷积层池化层听得云里雾里的,这一章正好帮我讲解了基础的知识。 第六章:卷积神经网络 6.1 从全连接层到卷积 在之前的学习中&…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
