本文 AI 產出,尚未審核

ExpressJS (TypeScript) – Request 與 Response 操作

解析 JSON、Form URL Encoded Body


簡介

在使用 Express 建立 API 時,最常見的需求就是從 client 收到資料,然後依據這些資料進行商業邏輯或回傳結果。
現代前端(React、Vue、Angular)或行動端多半會以 JSONForm URL‑Encoded 的形式送出請求,若伺服器端無法正確解析,整個服務就會卡關。

本篇文章聚焦於 ExpressJS + TypeScript 中,如何正確、有效率地解析這兩種請求 Body。
我們會從核心概念說明、實作範例、常見陷阱與最佳實踐,最後帶出實務應用情境,幫助讀者在開發正式服務時少走彎路。


核心概念

1️⃣ 什麼是 Body Parser?

在 HTTP 協定中,GET 請求的參數通常放在 URL Query String,而 POSTPUTPATCH 等方法的資料則放在 Body
Node.js 原生的 http 模組只會把 Body 當成 原始的 Buffer,開發者必須自行轉換成可讀的物件。

Express 內建的 express.json()express.urlencoded() 中介軟體(middleware)正是為了 自動解析 這兩種常見格式而設計的。
在 TypeScript 專案中,我們只需要在 app.ts(或 index.ts)裡先掛載這些中介軟體,之後的路由處理函式就能直接使用 req.body

⚠️ 小提醒:中介軟體的掛載順序很重要,必須在路由之前完成。


2️⃣ 解析 JSON Body

2.1 為什麼使用 JSON?

  • 可讀性高:鍵值對的結構直觀。
  • 跨平台:大多數語言都有成熟的 JSON 序列化/反序列化函式庫。
  • 支援巢狀結構:可以直接傳遞陣列、物件等複雜資料。

2.2 設定 express.json()

// src/app.ts
import express, { Request, Response, NextFunction } from 'express';

const app = express();

// 1️⃣ 解析 JSON body,預設限制 100KB(可自行調整)
app.use(express.json({
  limit: '1mb',               // 限制請求體大小,防止 DDOS 攻擊
  strict: true,              // 僅接受 object 或 array,其他型別會拋錯
  inflate: true,             // 支援 gzip/deflate 壓縮的 body
}));

export default app;

2.3 在路由中使用

// src/routes/user.ts
import { Router, Request, Response } from 'express';
const router = Router();

/**
 * POST /api/users
 * 接收 JSON 格式的使用者資料,回傳建立結果
 */
router.post('/users', (req: Request, res: Response) => {
  // 直接取得已解析好的物件
  const { name, email, age } = req.body as {
    name: string;
    email: string;
    age?: number;
  };

  // 基本驗證(實務上建議使用 Joi/Zod)
  if (!name || !email) {
    return res.status(400).json({ error: 'Name and email are required.' });
  }

  // TODO: 寫入資料庫或其他商業邏輯
  const createdUser = { id: Date.now(), name, email, age };

  // 回傳成功訊息
  return res.status(201).json(createdUser);
});

export default router;

重點:在 TypeScript 中,使用 as 斷言或自訂介面 (interface) 來描述 req.body,可以得到編譯時的型別檢查,減少執行時錯誤。


3️⃣ 解析 Form URL Encoded Body

3.1 何時會用到?

  • HTML <form> 提交(未加 enctype="multipart/form-data" 時的預設行為)。
  • 某些舊版 API(例如 OAuth 1.0a)仍採用 application/x-www-form-urlencoded
  • 簡單的鍵值對資料(不需要巢狀結構)時,傳輸效率略高於 JSON。

3.2 設定 express.urlencoded()

// src/app.ts(接續前面的設定)
app.use(express.urlencoded({
  extended: false,   // false => 使用 querystring 解析(不支援巢狀),true => 使用 qs
  limit: '500kb',    // 設定上限,避免過大請求
}));
  • extended: false 時,req.body 只能解析一層的鍵值對,例如 name=John&age=30
  • extended: true 時,會使用 qs 套件支援巢狀結構,如 user[name]=John&user[age]=30

3.3 路由範例

// src/routes/auth.ts
import { Router, Request, Response } from 'express';
const router = Router();

/**
 * POST /api/login
 * 接收表單編碼的帳號密碼
 */
router.post('/login', (req: Request, res: Response) => {
  // 這裡的 body 已經是鍵值對物件
  const { username, password } = req.body as {
    username: string;
    password: string;
  };

  if (!username || !password) {
    return res.status(400).json({ error: 'Missing username or password.' });
  }

  // 假設驗證成功
  const token = 'fake-jwt-token';
  return res.json({ token });
});

export default router;

4️⃣ 讓 TypeScript 更安全:型別與驗證

純粹靠 as 斷言會失去部分執行時檢查,建議結合 驗證函式庫(如 ZodJoiclass-validator):

// src/validation/user.ts
import { z } from 'zod';

export const createUserSchema = z.object({
  name: z.string().min(1, 'Name cannot be empty'),
  email: z.string().email('Invalid email address'),
  age: z.number().int().positive().optional(),
});

在路由中使用:

import { createUserSchema } from '../validation/user';
import { Request, Response } from 'express';

router.post('/users', (req: Request, res: Response) => {
  const parseResult = createUserSchema.safeParse(req.body);
  if (!parseResult.success) {
    const errors = parseResult.error.format();
    return res.status(422).json({ errors });
  }

  const user = parseResult.data;
  // ... 實作建立使用者
});

好處:即使前端送來不符合結構的資料,伺服器仍能在 第一時間 回傳明確錯誤,避免不必要的程式錯誤或 DB 異常。


常見陷阱與最佳實踐

陷阱 可能的後果 解決方式
忘記掛載 body parser req.bodyundefined,導致 500 錯誤 確保 app.use(express.json())app.use(express.urlencoded()) 在路由之前
限制過低 (limit) 大檔案或大量欄位被截斷,回傳 413 Payload Too Large 根據實際需求調整 limit,或在前端加檔案大小檢查
extended: false 卻需要巢狀結構 只能取得第一層鍵值,深層資料遺失 使用 extended: true 或改用 JSON 傳輸
未處理錯誤的 JSON (SyntaxError: Unexpected token) 請求直接斷線,客戶端無法得知錯誤原因 加入錯誤中介軟體捕捉 SyntaxError,回傳 400
直接使用 any 失去 TypeScript 的型別保護,執行時更易出錯 使用介面 (interface) 或驗證函式庫來限定型別

錯誤中介軟體範例

// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';

export function jsonErrorHandler(
  err: any,
  req: Request,
  res: Response,
  next: NextFunction
) {
  if (err instanceof SyntaxError && 'body' in err) {
    // 這是 express.json() 解析失敗的錯誤
    return res.status(400).json({ error: 'Invalid JSON payload' });
  }
  // 交給下游錯誤處理
  next(err);
}

app.ts 中掛載:

import { jsonErrorHandler } from './middleware/errorHandler';
app.use(jsonErrorHandler);

實際應用場景

  1. 建立使用者註冊 API
    前端使用 fetchaxios 送出 JSON,伺服器端透過 express.json() 解析,結合 Zod 進行欄位驗證,最後寫入資料庫。

  2. OAuth2 密碼授權(Password Grant)
    客戶端會以 application/x-www-form-urlencoded 方式送出 grant_type, username, password,此時 express.urlencoded({ extended: false }) 即可正確解析。

  3. Webhook 接收
    多數第三方服務(如 Stripe、GitHub)在發送 webhook 時會使用 application/json,因此必須先確保 express.json() 已啟用,且對外部來源的 JSON 做簽章驗證。

  4. 舊版表單系統遷移
    現有系統仍保留 HTML <form>,但新後端想要統一使用 TypeScript,於是同時支援 urlencodedjson,只要在路由裡以 req.body 取值即可,維持相容性。


總結

  • Express 內建的 express.json()express.urlencoded() 是解析 JSONForm URL‑Encoded 請求的核心工具。
  • TypeScript 專案中,透過 型別斷言介面驗證函式庫(如 Zod)能讓 req.body 的使用更安全、更具可讀性。
  • 正確設定 limitextended、錯誤中介軟體,並遵守 掛載順序,可以避免常見的「Body 為 undefined」或「JSON 解析失敗」等問題。
  • 了解何時應使用 JSON、何時使用 Form URL‑Encoded,配合實務需求(API、Webhook、舊版表單),讓你的 Express 應用既 彈性可靠

掌握以上概念與實作技巧,你就能在 Express + TypeScript 的開發中,輕鬆處理各式請求資料,為前端與其他服務提供穩定、可預測的 API 介面。祝開發順利 🚀