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 从全连接层到卷积 在之前的学习中&…...

JS中递归函数的理解及展开运算符在递归种的运用理解
<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>递归函数</title> </head> <body> <script>const list ["你好", "吃饭了吗",["好",[[&qu…...

人工智能学习用的电脑安装cuda、torch、conda等软件,版本的选择以及多版本切换
接触人工智能的学习三个月了,每天与各种安装包作斗争,缺少依赖包、版本高了、版本低了、不兼容了、系统做一半从头再来了。。。这些都是常态。三个月把单位几台电脑折腾了不下几十次安装,是时候总结一下踩过的坑和积累的经验了。 以一个典型的…...

提高身份证 OCR 识别 API 接口的准确性的方法
身份证OCR识别API接口能够快速、准确地识别并提取身份证上的文字信息,包括姓名、性别、民族、出生日期、住址、身份证号、签发机关、有效期限等关键内容,将其转化为计算机可处理的结构化数据,从而实现身份证信息的自动化录入和处理࿰…...

PHP面向对象
在 PHP 中,面向对象编程(Object-Oriented Programming,简称 OOP)是一种编程范式,它使用“对象”来组织和设计代码。对象是类的实例,类是定义对象特征和行为的蓝图。面向对象编程的主要目标是提高代码的可重…...

Tomcat新手成长之路:安装部署优化全解析(下)
接上篇《Tomcat新手成长之路:安装部署优化全解析(上)》: link 文章目录 7.应用部署7.1.上下文7.2.启动时进行部署7.3.动态应用部署 8.Tomcat 类加载机制8.1.简介8.2.类加载器定义8.3.XML解析器和 Java 9.JMS监控9.1.简介9.2.启用 JMX 远程监…...

GPT 1到4代的演进笔记
1. GPT-1 标题是 Improving Language Understanding by Generative Pre-Training. 发表于 2018.02, 比 bert(发布于 2018.10) 早了半年. 1.1 动机 困难:NLU 任务是多样的, 有 {textual entailment, question answering, semantic similarity assessment, document classifica…...

vitepress组件库文档项目 markdown语法大全(修正版)
#上次总结的 有些语法是用在markdown文档中的 使用到vitepress项目中有些语法可能有出入 于是我再总结一版 vitepress项目中的markdown语法大全 在阅读本章节之前,请确保你已经对 Markdown 有所了解。如果你还不了解 Markdown ,请先学习一些Markdown 教…...

Vue3技术开发,使用纯CSS3动手制作一个3D环绕的相册展示效果,支持传入任意图片.3D轮播相册的组件
主要讲述封装一个3D轮播相册的组件,效果图如下,仅仅传入一个图片的数组即可,效果如下: 使用Vue3技术开发,支持传入任意张数的图片。 使用方法 <template><Swiper :list"list" /> </templat…...

LeetCode 力扣 热题 100道(十五)搜索插入位置(C++)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 代码如下所示: class Solution { public:int searchIns…...

【035】基于51单片机俄罗斯方块游戏机【Proteus仿真+Keil程序+报告+原理图】
☆、设计硬件组成:51单片机最小系统LCD12864液晶显示按键控制。 1、设计采用STC89C52、AT89C52、AT89S52作为主控芯片,采用LCD12864液晶作为显示,大屏显示就是刺激; 2、游戏设置十个关卡,每个关卡累计99分即可进入下…...