首页 / Node.js 教程 / 处理 GET 与 POST

Node.js 教程

处理 GET 与 POST

本教程共 76 篇 · 第 27 篇 · 更新于 2026-07-25 · 约 6 分钟阅读

Node.jsHTTPGETPOST请求体

27. 处理 GET 与 POST

本节目标:解析查询字符串、读取 POST 请求体、处理表单和 JSON 数据。

上一章搭了一个能回显请求的服务器,但离「可用」还差一点:GET 请求的查询参数怎么拆?POST 过来的表单、JSON 又怎么读?这一章就把这两件事彻底搞懂。

解析 GET 查询参数

浏览器地址栏里那种 ?name=Tom&age=20 的字符串,在 Node.js 里最好用 WHATWG 标准的 URL 对象来解析。url.parse 是旧时代的产物,v24 里虽然还能跑,但官方已经不推荐,新项目别碰。

用 URL 构造函数解析

import http from 'node:http';

const server = http.createServer((req, res) => {
  // req.url 只有路径和查询字符串,没有协议和主机
  // 所以需要提供一个 base URL 来补全
  const baseUrl = `http://${req.headers.host}`;
  const parsed = new URL(req.url, baseUrl);

  console.log('Pathname:', parsed.pathname);
  console.log('Search:', parsed.search);
  console.log('Name:', parsed.searchParams.get('name'));

  res.end('Check console');
});

server.listen(3000);

URLSearchParams 提供了不少实用方法:

parsed.searchParams.get('name');      // 取单个值
parsed.searchParams.getAll('tag');    // 同名参数取全部
parsed.searchParams.has('age');       // 判断是否存在
parsed.searchParams.append('new', '1'); // 追加
parsed.searchParams.toString();       // 转回查询字符串
Tip

如果查询参数里带中文或特殊符号,浏览器会自动 encode。URLSearchParamsget() 时会自动 decode,不用你手动 decodeURIComponent

一个实用的查询参数示例

下面这个服务器根据 ?type=json|html 返回不同格式的数据:

import http from 'node:http';

const server = http.createServer((req, res) => {
  const parsed = new URL(req.url, `http://${req.headers.host}`);

  if (parsed.pathname !== '/user') {
    res.statusCode = 404;
    res.end('Not Found');
    return;
  }

  const type = parsed.searchParams.get('type') || 'json';
  const name = parsed.searchParams.get('name') || 'Guest';

  if (type === 'html') {
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.end(`<h1>Hello, ${name}</h1>`);
  } else {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ name }));
  }
});

server.listen(3000, () => {
  console.log('Try: http://localhost:3000/user?name=Tom&type=html');
});

读取 POST 请求体

POST 的数据不在 URL 里,而是在请求体中。客户端通常会通过 Content-Type 头部告诉服务端 body 的格式,常见的有三种:

  • application/json
  • application/x-www-form-urlencoded
  • text/plain

服务端的第一步,是把流里的二进制片段拼成完整的字符串。

通用 body 收集函数

我写了一个小技巧,几乎每个原生 HTTP 项目都会用到:

function collectBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => resolve(Buffer.concat(chunks).toString()));
    req.on('error', reject);
  });
}

Buffer.concat 而不是字符串拼接,是为了避免多字节字符被截断导致的乱码。这个坑我踩过不止一次。

处理 JSON 数据

import http from 'node:http';

const server = http.createServer(async (req, res) => {
  if (req.method !== 'POST' || req.url !== '/api/data') {
    res.statusCode = 404;
    res.end('Not Found');
    return;
  }

  try {
    const body = await collectBody(req);
    const data = JSON.parse(body);

    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ received: data }));
  } catch (err) {
    res.statusCode = 400;
    res.end(JSON.stringify({ error: 'Invalid JSON' }));
  }
});

server.listen(3000);

测试:

curl -X POST http://localhost:3000/api/data \
  -H "Content-Type: application/json" \
  -d '{"name":"Tom","age":20}'

处理表单数据(URL 编码)

HTML 表单默认以 application/x-www-form-urlencoded 提交,数据格式跟查询字符串一样:name=Tom&age=20。Node.js 内置了 querystring 模块来解析它。

import http from 'node:http';
import querystring from 'node:querystring';

const server = http.createServer(async (req, res) => {
  if (req.method !== 'POST' || req.url !== '/submit') {
    res.statusCode = 404;
    res.end('Not Found');
    return;
  }

  const body = await collectBody(req);
  const data = querystring.parse(body);

  res.setHeader('Content-Type', 'text/html; charset=utf-8');
  res.end(`<p>收到:姓名 ${data.name},年龄 ${data.age}</p>`);
});

server.listen(3000);

querystring.parse 会尽量智能转换,但遇到复杂结构时它会把同名键变成数组。如果你需要更强大的表单解析(比如文件上传),后面章节会讲 multer,这里先掌握原生方案。

一个完整的表单示例

光讲 API 有点干,我们来做一个「能跑」的完整例子。服务端提供一个表单页面,用户填写后 POST 回来,服务端再展示结果。

import http from 'node:http';
import querystring from 'node:querystring';

function collectBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => resolve(Buffer.concat(chunks).toString()));
    req.on('error', reject);
  });
}

const formHtml = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>注册</title>
</head>
<body>
  <h1>用户注册</h1>
  <form method="POST" action="/register">
    <p>用户名:<input name="username" required></p>
    <p>邮箱:<input type="email" name="email" required></p>
    <p><button type="submit">提交</button></p>
  </form>
</body>
</html>
`;

const server = http.createServer(async (req, res) => {
  if (req.method === 'GET' && req.url === '/') {
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.end(formHtml);
    return;
  }

  if (req.method === 'POST' && req.url === '/register') {
    const body = await collectBody(req);
    const data = querystring.parse(body);

    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.end(`
      <h1>注册成功</h1>
      <p>用户名:${data.username}</p>
      <p>邮箱:${data.email}</p>
      <a href="/">返回</a>
    `);
    return;
  }

  res.statusCode = 404;
  res.end('Not Found');
});

server.listen(3000, () => {
  console.log('Open http://localhost:3000');
});

这个例子虽然简单,但已经包含了 GET 页面渲染、POST 表单接收、数据解析和 HTML 响应的完整链路。你完全可以在这个骨架上继续扩展。

关于 Content-Type 的注意事项

客户端发来的 Content-Type 可能带有 charset 后缀,比如 application/json; charset=utf-8。如果你需要判断类型,别用严格相等:

const contentType = req.headers['content-type'] || '';

if (contentType.startsWith('application/json')) {
  // 解析 JSON
}

if (contentType.startsWith('application/x-www-form-urlencoded')) {
  // 解析表单
}