express.urlencoded和fetch结合使用

一、核心前提

express.urlencoded 仅解析 Content-Type: application/x-www-form-urlencoded 格式的请求体,因此用 fetch 发送请求时,必须满足两个条件:

  1. 请求体需构造为「URL 编码的键值对字符串」(如 username=zhangsan&age=20),而非 JSON 对象。
  2. 手动设置请求头 Content-Type: application/x-www-form-urlencodedfetch 不会自动为该格式设置请求头)。

二、步骤1:后端 Express 配置

首先搭建支持 application/x-www-form-urlencoded 解析的后端服务,核心是注册 express.urlencoded 中间件。

完整后端代码

javascript 复制代码
// server.js
const express = require('express');
const app = express();

// 1. 注册 urlencoded 中间件(关键:extended: true 支持嵌套数据)
app.use(express.urlencoded({ extended: true }));

// 2. 解决跨域(前端 fetch 大概率存在跨域,需配置)
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有来源(开发环境)
  res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  next();
});

// 3. 定义接收表单数据的接口
app.post('/api/user', (req, res) => {
  console.log('解析后的请求体:', req.body);
  // req.body 已挂载解析后的数据
  const { username, age, hobby } = req.body;
  
  // 返回响应
  res.json({
    code: 200,
    message: '数据接收成功',
    data: { username, age, hobby }
  });
});

// 启动服务
const port = 3000;
app.listen(port, () => {
  console.log(`后端服务运行在 http://localhost:${port}`);
});

运行后端

  1. 初始化项目:npm init -y
  2. 安装 Express:npm install express
  3. 启动服务:node server.js

三、步骤2:前端 fetch 发送请求

前端需手动构造 URL 编码字符串,并设置正确的请求头,以下提供 3 种常用写法。

写法1:手动拼接键值对(简单场景)

适合数据量少、无嵌套的场景,直接拼接字符串作为请求体。

html 复制代码
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>fetch + express.urlencoded</title>
</head>
<body>
  <button onclick="submitData()">提交数据</button>

  <script>
    // 提交数据函数
    async function submitData() {
      try {
        // 1. 准备要发送的数据
        const username = '张三';
        const age = 22;
        const hobby = '篮球';

        // 2. 手动拼接为 URL 编码字符串(核心:key=value&key=value 格式)
        const requestBody = `username=${encodeURIComponent(username)}&age=${age}&hobby=${encodeURIComponent(hobby)}`;
        // 注意:使用 encodeURIComponent 编码特殊字符(如中文、空格、@ 等),避免解析异常

        // 3. 发送 fetch 请求
        const response = await fetch('http://localhost:3000/api/user', {
          method: 'POST', // 必须指定 POST 方法(GET 无请求体)
          headers: {
            // 关键:设置 Content-Type 为 application/x-www-form-urlencoded
            'Content-Type': 'application/x-www-form-urlencoded'
          },
          body: requestBody // 传入拼接好的 URL 编码字符串
        });

        // 4. 解析响应
        const result = await response.json();
        console.log('后端响应:', result);
        alert(`成功:${JSON.stringify(result.data)}`);
      } catch (error) {
        console.error('请求失败:', error);
        alert('请求失败');
      }
    }
  </script>
</body>
</html>

写法2:使用 URLSearchParams 构造(推荐,简洁高效)

URLSearchParams 是浏览器内置 API,可自动将对象转为 URL 编码字符串,无需手动拼接,还能自动处理特殊字符编码。

javascript 复制代码
async function submitData() {
  try {
    // 1. 准备原始数据对象
    const formData = {
      username: '李四',
      age: 25,
      hobby: '音乐',
      // 支持嵌套对象(后端需开启 extended: true 才能解析)
      address: {
        city: '北京',
        area: '朝阳'
      }
    };

    // 2. 用 URLSearchParams 构造请求体
    const searchParams = new URLSearchParams();
    // 普通键值对
    searchParams.append('username', formData.username);
    searchParams.append('age', formData.age);
    searchParams.append('hobby', formData.hobby);
    // 嵌套对象(需按 user[city] 格式 append)
    searchParams.append('address[city]', formData.address.city);
    searchParams.append('address[area]', formData.address.area);

    // 3. 发送请求(searchParams 可直接作为 body 参数)
    const response = await fetch('http://localhost:3000/api/user', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: searchParams // 自动转为 URL 编码字符串
    });

    const result = await response.json();
    console.log('后端响应:', result);
    alert(`成功:${JSON.stringify(result.data)}`);
  } catch (error) {
    console.error('请求失败:', error);
  }
}
简化写法(适用于单层对象)

若数据无嵌套,可直接传入对象(部分浏览器支持,兼容性略差,推荐用 append 写法):

javascript 复制代码
const formData = { username: '王五', age: 30 };
const searchParams = new URLSearchParams(formData); // 自动转为 username=王五&age=30

写法3:处理数组数据(后端 extended: true 生效)

express.urlencoded({ extended: true }) 支持解析数组,前端需按 hobby[0]hobby[1] 格式构造。

javascript 复制代码
async function submitData() {
  try {
    const formData = {
      username: '赵六',
      age: 28,
      hobby: ['游戏', '读书', '运动'] // 数组数据
    };

    const searchParams = new URLSearchParams();
    searchParams.append('username', formData.username);
    searchParams.append('age', formData.age);
    // 数组处理:循环 append
    formData.hobby.forEach((item, index) => {
      searchParams.append(`hobby[${index}]`, item);
    });

    const response = await fetch('http://localhost:3000/api/user', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: searchParams
    });

    const result = await response.json();
    console.log('后端响应:', result);
    // 后端 req.body.hobby 会是 ['游戏', '读书', '运动']
  } catch (error) {
    console.error('请求失败:', error);
  }
}

四、完整前后端联动效果

  1. 前端点击按钮发送请求,请求体格式为 username=张三&age=22&hobby=篮球
  2. 后端 express.urlencoded 解析后,req.body{ username: '张三', age: '22', hobby: '篮球' }
  3. 后端返回 JSON 响应,前端解析后弹出提示并在控制台打印结果。

五、常见坑点与解决方案

坑点1:req.bodyundefined

  • 原因:
    1. 前端未设置 Content-Type: application/x-www-form-urlencoded
    2. 后端未注册 express.urlencoded 中间件。
    3. 后端中间件注册在路由之后(执行顺序问题)。
  • 解决方案:
    1. 前端 fetch 中明确设置 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    2. 后端先注册 app.use(express.urlencoded({ extended: true })),再定义路由。

坑点2:中文/特殊字符乱码

  • 原因:未对特殊字符进行 URL 编码,导致解析异常。
  • 解决方案:
    1. 手动拼接时使用 encodeURIComponent() 编码每个值。
    2. 优先使用 URLSearchParams,自动处理编码问题。

坑点3:嵌套对象/数组解析失败

  • 原因:后端 extended 配置为 false(默认旧版 Express),querystring 库不支持嵌套解析。
  • 解决方案:后端必须设置 app.use(express.urlencoded({ extended: true }))

坑点4:fetch 发送 GET 请求带请求体

  • 原因:GET 请求默认没有请求体,即使传入 body 参数也会被忽略。
  • 解决方案:
    1. 表单数据用 POST 方法发送(推荐)。
    2. 若需用 GET,将数据拼接到 URL 后(如 http://localhost:3000/api/user?username=张三&age=22)。

六、与 fetch 发送 JSON 格式的区别

为了避免混淆,这里对比两者的差异:

对比项 发送 application/x-www-form-urlencoded(本文) 发送 application/json
后端中间件 express.urlencoded({ extended: true }) express.json()
前端 Content-Type application/x-www-form-urlencoded application/json
前端请求体格式 URL 编码字符串(如 a=1&b=2 JSON 字符串(如 JSON.stringify({a:1,b:2})
适用场景 兼容传统表单、简单数据提交 现代接口、复杂嵌套数据提交

总结

  1. 核心要求:前端 fetch 需设置 Content-Type: application/x-www-form-urlencoded,并将请求体转为 URL 编码字符串。
  2. 推荐用法:使用 URLSearchParams 构造请求体,简洁且能自动处理特殊字符编码,后端需配置 express.urlencoded({ extended: true })
  3. 避坑要点:中间件注册在路由之前、使用 POST 方法、嵌套/数组数据需开启 extended: true、特殊字符需编码。
  4. 跨域处理:后端需配置 CORS 响应头,否则前端 fetch 会出现跨域错误。
相关推荐
GDAL3 小时前
express.json 深入全面讲解教程
json·express
GDAL4 小时前
Express 中 CORS 跨域问题解决教程
express·cors
GDAL4 小时前
express.text和fetch配合使用深入全面教程
express·text
GDAL5 小时前
HTML Form 深入全面讲解教程
html·form
GDAL1 天前
Express POST 请求深入全面讲解教程
express
正经教主2 天前
【Trae+AI】和Trae学习搭建App_2.1:第3章·手搓后端基础框架Express
人工智能·后端·学习·express
你真的可爱呀5 天前
2.Express 核心语法与路由
中间件·node.js·express
骚团长5 天前
SQL server 配置管理器-SQL server 服务-远程过程调试失败 [0x800706be]-(Express LocalDB卸载掉)完美解决!
java·服务器·express
你真的可爱呀6 天前
1.基础环境搭建与核心认知
node.js·express