node.js基础学习-express框架-路由及中间件(十)

一、前言

Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建,提供了一系列用于构建 Web 应用程序和 API 的功能,使开发者能够更高效地处理 HTTP 请求和响应,专注于业务逻辑的实现。

其特点包括简单易用、中间件机制丰富、路由系统灵活等。通过使用 Express,可以快速搭建服务器,处理不同类型的请求,如网页渲染、数据接口提供等多种功能。

二、路由

1. 基本的路由格式

一个基本的 Express 路由由 HTTP 方法(如app.getapp.post等)、路径(如'/''/about'等)和一个或多个回调函数组成。例如:

js 复制代码
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. 中间件的使用方式

单个中间件:在路由处理函数中,可以有一个或多个中间件。例如:

js 复制代码
app.get('/home', (req, res, next) => {
    console.log('This is a middleware');
    next();
}, (req, res) => {
    res.send('This is the home page');
});

这里第一个函数是中间件,它先打印一条日志,然后调用next()将控制权传递给下一个函数(这里是路由处理函数),用于发送响应。如果不调用next()函数,下一个中间件将不会被执行。

中间件数组:也可以将多个中间件组合成一个数组来使用。例如:

js 复制代码
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路径时,会依次执行func1func2func3这三个中间件,最后执行路由处理函数来发送响应。

2. 中间件之间的传值

中间件可以通过reqres对象在中间件之间传递值。例如:

js 复制代码
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.nameres.age设置了值,然后在func5中间件中可以获取这些值来生成响应。

3. 不同类型的中间件

3.1 应用级中间件

通过app.use()方法来添加应用级中间件,它可以应用于整个应用程序或者特定的路径。如果没有指定路径,中间件会应用于所有的请求路径。例如:

js 复制代码
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 特定路径的应用级中间件

可以指定中间件应用的路径,这样中间件只会对匹配该路径及其子路径的请求起作用。例如:

js 复制代码
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()在特定路由路径下注册,在路由处理函数之前执行,用于对该路由的请求进行预处理等操作。例如:

js 复制代码
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参数用于接收错误对象。错误处理中间件应该放在所有其他中间件和路由定义之后,这样才能捕获它们抛出的错误。例如:

js 复制代码
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 格式时非常有用。例如:

js 复制代码
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 表单提交的数据。例如:

js 复制代码
app.use(express.urlencoded({ extended: false }));
app.post('/form - data', (req, res) => {
    const formData = req.body;
    // 处理接收到的表单数据
    res.send('Form data received');
});

3.6 第三方中间件

**morgan:**用于日志记录,它可以记录每个请求的详细信息,如请求方法、请求路径、响应状态码等。例如:

js 复制代码
const morgan = require('morgan');
app.use(morgan('combined'));

这里morgan('combined')是一种日志格式选项,它会记录详细的请求信息。morgan还有其他日志格式,如'dev'(适合开发环境)、'common'等,开发者可以根据实际需求选择。

**cors:**用于解决跨域资源共享问题。例如:

js 复制代码
const cors = require('cors');
app.use(cors({
    origin: 'http://example.com',
    methods: ['GET', 'POST'],
}));

这个配置允许来自http://example.com的请求使用GETPOST方法进行跨域访问。可以根据具体的业务场景调整origin(允许的源)、methods(允许的请求方法)等参数来满足跨域需求。

四、 模块化的路由中间件

express.Router是 Express 框架中的一个重要组件,它提供了一种模块化的方式来定义路由。使用Router可以将路由分组并封装到独立的模块中,这有助于组织大型应用程序的路由结构,使代码更加清晰、易于维护和扩展。

1. 创建和使用express.Router实例

创建实例 :首先,需要创建一个Router实例。例如:

js 复制代码
const express = require('express');
const router = express.Router();

定义路由 :在Router实例上可以像在主app对象上一样定义各种 HTTP 方法的路由。例如:

js 复制代码
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 应用上。例如:

js 复制代码
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所定义的所有路由上。例如:

js 复制代码
const loggerMiddleware = (req, res, next) => {
    console.log(`Received a request for ${req.url}`);
    next();
};
router.use(loggerMiddleware);

这里定义了一个日志记录中间件loggerMiddleware,并通过router.use()将其应用到router上。这样,router中所有的路由在被访问时,都会先执行这个日志记录中间件。

在特定路由中使用中间件 :也可以在Router的特定路由中使用中间件。例如:

js 复制代码
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用于处理每个用户组内用户相关的路由。

js 复制代码
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部分的路由。

相关推荐
PP东2 小时前
ES6学习Generator 函数(生成器)(八)
javascript·学习·es6
小屁不止是运维4 小时前
麒麟操作系统服务架构保姆级教程(二)ssh远程连接
linux·运维·服务器·学习·架构·ssh
follycat5 小时前
bestphp‘s revenge
学习·web安全
职业考试资料墙5 小时前
二级建造师考试题库及答案
学习·考试·题库
Aughts6 小时前
基础电路的学习
学习
韩俊强7 小时前
使用Docker部署一个Node.js项目
docker·容器·node.js
岳不谢8 小时前
华为DHCP高级配置学习笔记
网络·笔记·网络协议·学习·华为
爱吃西瓜的小菜鸡9 小时前
【C语言】抽空洗澡
c语言·开发语言·学习·算法
lover_putter11 小时前
ai学习报告:训练
人工智能·学习
123yhy传奇11 小时前
【学习总结|DAY020】Java FIle、字符集、IO流
java·开发语言·学习