一、前言
Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建,提供了一系列用于构建 Web 应用程序和 API 的功能,使开发者能够更高效地处理 HTTP 请求和响应,专注于业务逻辑的实现。
其特点包括简单易用、中间件机制丰富、路由系统灵活等。通过使用 Express,可以快速搭建服务器,处理不同类型的请求,如网页渲染、数据接口提供等多种功能。
二、路由
1. 基本的路由格式
一个基本的 Express 路由由 HTTP 方法(如app.get
、app.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
路径时,会依次执行func1
、func2
、func3
这三个中间件,最后执行路由处理函数来发送响应。
2. 中间件之间的传值
中间件可以通过req
或res
对象在中间件之间传递值。例如:
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.name
和res.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
的请求使用GET
和POST
方法进行跨域访问。可以根据具体的业务场景调整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
部分的路由。