MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js 在 Express 中处理 GET 和 POST 请求

2021-11-103.5k 阅读

一、Express 框架简介

Express 是基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供了一系列强大的特性,帮助开发者快速地搭建 Web 应用和 API。Express 之所以深受欢迎,原因在于其简洁的路由系统、中间件支持以及对各种 HTTP 功能的良好封装。

1.1 安装 Express

在使用 Express 之前,我们需要先安装它。假设你已经安装了 Node.js,打开终端,进入你的项目目录,然后运行以下命令:

npm install express --save

--save 选项会将 Express 添加到 package.json 文件的 dependencies 中,方便以后项目部署时重新安装依赖。

1.2 创建基本的 Express 应用

安装完成后,我们可以创建一个简单的 Express 应用。在项目目录下创建一个 app.js 文件,输入以下代码:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,我们首先引入了 Express 模块,然后创建了一个 Express 应用实例 app。接着,我们定义了一个根路径('/')的 GET 请求处理函数,当用户访问根路径时,会返回 Hello, World!。最后,应用监听在 3000 端口上。

二、处理 GET 请求

GET 请求是最常见的 HTTP 请求方法之一,通常用于从服务器获取数据。在 Express 中处理 GET 请求非常简单,主要通过 app.get() 方法来定义路由。

2.1 基本 GET 请求处理

我们在前面已经看到了一个简单的 GET 请求处理示例:

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

这里,app.get() 方法接受两个参数,第一个参数是路由路径,第二个参数是请求处理函数。请求处理函数接受两个参数,req(请求对象)和 res(响应对象)。req 对象包含了关于请求的所有信息,例如请求头、请求参数等;res 对象则用于向客户端发送响应。

2.2 动态路由参数

在实际应用中,我们经常需要处理带有动态参数的 URL。例如,我们可能有一个用户详情页面,URL 格式为 /users/:id,其中 :id 就是动态参数。在 Express 中,我们可以这样定义路由:

app.get('/users/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`User ID: ${userId}`);
});

在这个例子中,req.params 是一个对象,包含了 URL 中的动态参数。当用户访问 /users/123 时,req.params.id 的值就是 123,服务器会返回 User ID: 123

2.3 查询字符串参数

除了动态路由参数,GET 请求还经常使用查询字符串来传递参数。例如,/search?keyword=javascript&page=1。在 Express 中,我们可以通过 req.query 对象来获取查询字符串参数:

app.get('/search', (req, res) => {
    const keyword = req.query.keyword;
    const page = req.query.page;
    res.send(`Search keyword: ${keyword}, Page: ${page}`);
});

当用户访问 /search?keyword=javascript&page=1 时,req.query.keyword 的值为 javascriptreq.query.page 的值为 1,服务器会返回相应的信息。

三、处理 POST 请求

POST 请求通常用于向服务器提交数据,例如用户注册、登录等场景。与 GET 请求不同,POST 请求的数据不会显示在 URL 中,而是包含在请求体(request body)中。

3.1 解析请求体

在 Express 中处理 POST 请求,首先需要解析请求体。从 Express 4.16.0 版本开始,内置了 express.json()express.urlencoded() 中间件来处理 JSON 和 URL-encoded 格式的数据。

3.1.1 处理 JSON 格式数据

要处理 JSON 格式的 POST 请求数据,我们需要使用 express.json() 中间件。在 app.js 文件中添加如下代码:

app.use(express.json());

app.post('/api/data', (req, res) => {
    const data = req.body;
    res.send(`Received data: ${JSON.stringify(data)}`);
});

在上述代码中,app.use(express.json()) 用于全局注册 express.json() 中间件,这样 Express 就能够解析 JSON 格式的请求体。在 app.post('/api/data') 路由处理函数中,我们通过 req.body 获取解析后的 JSON 数据。

3.1.2 处理 URL-encoded 格式数据

对于 URL-encoded 格式的数据,我们使用 express.urlencoded() 中间件。代码如下:

app.use(express.urlencoded({ extended: true }));

app.post('/form', (req, res) => {
    const username = req.body.username;
    const password = req.body.password;
    res.send(`Username: ${username}, Password: ${password}`);
});

express.urlencoded({ extended: true }) 表示使用更强大的 qs 库来解析 URL-encoded 数据。extended 选项为 true 时,支持对象和数组的嵌套解析;为 false 时,只支持简单的键值对解析。在 app.post('/form') 路由处理函数中,通过 req.body 获取表单数据。

3.2 完整 POST 请求处理示例

下面我们来看一个完整的用户注册的 POST 请求处理示例。假设我们有一个 HTML 表单:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User Registration</title>
</head>

<body>
    <form action="/register" method="post">
        <label for="username">Username:</label><br>
        <input type="text" id="username" name="username" required><br>
        <label for="password">Password:</label><br>
        <input type="password" id="password" name="password" required><br><br>
        <input type="submit" value="Register">
    </form>
</body>

</html>

在 Express 应用中处理这个表单提交:

app.use(express.urlencoded({ extended: true }));

app.post('/register', (req, res) => {
    const username = req.body.username;
    const password = req.body.password;
    // 这里可以进行数据库插入等操作
    res.send(`Registration successful. Username: ${username}, Password: ${password}`);
});

当用户填写表单并提交时,Express 应用会接收到 POST 请求,解析请求体中的数据,并返回相应的注册成功信息。

四、GET 和 POST 请求的区别及应用场景

4.1 区别

  1. 数据位置:GET 请求的数据通过 URL 的查询字符串传递,数据暴露在 URL 中,长度有限制;POST 请求的数据放在请求体中,理论上长度没有限制。
  2. 安全性:由于 GET 请求的数据在 URL 中可见,所以安全性较差,不适合传递敏感信息,如密码等;POST 请求相对更安全,因为数据在请求体中。
  3. 幂等性:GET 请求是幂等的,多次请求同一个 URL 应该返回相同的结果;POST 请求不是幂等的,多次提交可能会导致不同的结果,例如多次提交订单。

4.2 应用场景

  1. GET 请求:适合用于获取数据,如查询文章列表、获取用户信息等场景。由于其幂等性,适合用于浏览器的书签功能,方便用户快速访问相同的数据。
  2. POST 请求:常用于提交数据,如用户注册、登录、上传文件等场景。因为其安全性相对较高,且适合处理大量数据,所以在涉及敏感信息和大数据量传输时常用 POST 请求。

五、中间件在处理请求中的作用

中间件是 Express 应用中非常重要的概念,它可以对请求和响应进行预处理和后处理。在处理 GET 和 POST 请求时,中间件可以帮助我们实现很多功能,如日志记录、身份验证、错误处理等。

5.1 自定义中间件

我们可以自定义中间件函数。中间件函数接受 reqresnext 三个参数,next 用于将控制权传递给下一个中间件或路由处理函数。例如,我们创建一个简单的日志记录中间件:

const logger = (req, res, next) => {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    next();
};

app.use(logger);

在上述代码中,logger 中间件会在每个请求到达时记录请求的时间、方法和 URL。app.use(logger) 将这个中间件应用到整个应用中。

5.2 中间件顺序

中间件的顺序非常重要,因为 Express 是按照中间件注册的顺序依次执行的。例如,如果我们有一个身份验证中间件和一个请求处理路由:

const authMiddleware = (req, res, next) => {
    // 假设这里进行身份验证
    const isAuthenticated = true;
    if (isAuthenticated) {
        next();
    } else {
        res.status(401).send('Unauthorized');
    }
};

app.use(authMiddleware);

app.get('/protected', (req, res) => {
    res.send('This is a protected route');
});

在这个例子中,authMiddleware 会在 /protected 路由处理之前执行。如果用户未通过身份验证,就会返回 401 Unauthorized,不会执行 /protected 路由的处理函数。

5.3 错误处理中间件

错误处理中间件用于捕获应用中的异常。它与普通中间件的区别在于接受四个参数:errreqresnext。例如:

const errorHandler = (err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something went wrong!');
};

app.use(errorHandler);

当应用中任何地方抛出异常时,错误处理中间件会捕获并处理这个异常,返回一个友好的错误信息给客户端。

六、在 Express 中处理多个请求方法

Express 允许我们在同一个路由上处理多个请求方法。例如,我们可以在同一个 /users 路由上同时处理 GET 和 POST 请求:

app.route('/users')
   .get((req, res) => {
        // 处理 GET 请求,返回用户列表
        res.send('User list');
    })
   .post((req, res) => {
        // 处理 POST 请求,创建新用户
        res.send('Create new user');
    });

在上述代码中,app.route('/users') 创建了一个路由实例,然后通过 .get().post() 方法分别定义了 GET 和 POST 请求的处理函数。这种方式可以使代码更加清晰,特别是当一个路由需要处理多个请求方法时。

七、处理复杂的请求场景

在实际项目中,请求处理可能会更加复杂。例如,我们可能需要对请求进行参数验证、数据过滤、调用外部 API 等操作。

7.1 参数验证

我们可以使用第三方库,如 joi 来进行参数验证。首先安装 joi

npm install joi --save

然后在 Express 应用中进行参数验证:

const Joi = require('joi');

const userSchema = Joi.object({
    username: Joi.string().min(3).required(),
    password: Joi.string().min(6).required()
});

app.post('/register', async (req, res) => {
    const { error } = userSchema.validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }
    // 参数验证通过,进行后续处理
    res.send('Registration successful');
});

在上述代码中,我们使用 joi 定义了一个用户注册的参数验证 schema,然后在 app.post('/register') 路由处理函数中进行验证。如果验证失败,返回错误信息给客户端。

7.2 数据过滤

有时候我们需要对请求数据进行过滤,只保留合法的数据。例如,我们可以使用 lodash 库来进行数据过滤。首先安装 lodash

npm install lodash --save

然后在 Express 应用中进行数据过滤:

const _ = require('lodash');

app.post('/process', (req, res) => {
    const data = req.body;
    const allowedFields = ['field1', 'field2'];
    const filteredData = _.pick(data, allowedFields);
    res.send(`Filtered data: ${JSON.stringify(filteredData)}`);
});

在这个例子中,我们使用 _.pick() 方法从请求体数据中只提取了 allowedFields 中的字段。

7.3 调用外部 API

在处理请求时,我们可能需要调用外部 API 来获取额外的数据。例如,我们可以使用 axios 库来调用外部 API。首先安装 axios

npm install axios --save

然后在 Express 应用中调用外部 API:

const axios = require('axios');

app.get('/external-data', async (req, res) => {
    try {
        const response = await axios.get('https://example.com/api/data');
        res.send(response.data);
    } catch (error) {
        res.status(500).send('Error fetching external data');
    }
});

在上述代码中,我们使用 axios.get() 方法调用了一个外部 API,并将获取到的数据返回给客户端。如果调用过程中出现错误,返回错误信息。

八、性能优化与最佳实践

8.1 性能优化

  1. 缓存:对于一些不经常变化的数据,可以使用缓存来提高性能。例如,可以使用 express - cache - response 中间件来缓存响应数据。
  2. 压缩:启用 Gzip 压缩可以减少响应数据的大小,加快传输速度。在 Express 中,可以使用 compression 中间件来实现 Gzip 压缩:
const compression = require('compression');
app.use(compression());
  1. 异步处理:在处理 I/O 操作,如数据库查询、文件读取等时,尽量使用异步操作,避免阻塞事件循环。Express 本身就是基于异步 I/O 设计的,但在自定义中间件和路由处理函数中要注意正确使用异步操作。

8.2 最佳实践

  1. 代码结构:保持代码结构清晰,将路由、中间件等功能模块化。例如,可以将不同模块的路由放在不同的文件中,然后在主 app.js 文件中引入。
  2. 错误处理:统一的错误处理机制非常重要。除了前面提到的错误处理中间件,还可以在异步操作中使用 try - catch 块来捕获错误,避免未处理的异常导致应用崩溃。
  3. 日志记录:合理的日志记录有助于调试和监控应用。可以使用 winston 等日志库来记录请求日志、错误日志等。

通过以上内容,我们详细地了解了在 Express 中如何处理 GET 和 POST 请求,以及相关的一些高级特性和最佳实践。希望这些知识能够帮助你开发出更健壮、高效的 Node.js 应用。