ExpressJS (TypeScript) – Request 與 Response 操作
解析 JSON、Form URL Encoded Body
簡介
在使用 Express 建立 API 時,最常見的需求就是從 client 收到資料,然後依據這些資料進行商業邏輯或回傳結果。
現代前端(React、Vue、Angular)或行動端多半會以 JSON 或 Form URL‑Encoded 的形式送出請求,若伺服器端無法正確解析,整個服務就會卡關。
本篇文章聚焦於 ExpressJS + TypeScript 中,如何正確、有效率地解析這兩種請求 Body。
我們會從核心概念說明、實作範例、常見陷阱與最佳實踐,最後帶出實務應用情境,幫助讀者在開發正式服務時少走彎路。
核心概念
1️⃣ 什麼是 Body Parser?
在 HTTP 協定中,GET 請求的參數通常放在 URL Query String,而 POST、PUT、PATCH 等方法的資料則放在 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 斷言會失去部分執行時檢查,建議結合 驗證函式庫(如 Zod、Joi、class-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.body 為 undefined,導致 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);
實際應用場景
建立使用者註冊 API
前端使用fetch或axios送出 JSON,伺服器端透過express.json()解析,結合 Zod 進行欄位驗證,最後寫入資料庫。OAuth2 密碼授權(Password Grant)
客戶端會以application/x-www-form-urlencoded方式送出grant_type,username,password,此時express.urlencoded({ extended: false })即可正確解析。Webhook 接收
多數第三方服務(如 Stripe、GitHub)在發送 webhook 時會使用application/json,因此必須先確保express.json()已啟用,且對外部來源的 JSON 做簽章驗證。舊版表單系統遷移
現有系統仍保留 HTML<form>,但新後端想要統一使用 TypeScript,於是同時支援urlencoded與json,只要在路由裡以req.body取值即可,維持相容性。
總結
- Express 內建的
express.json()與express.urlencoded()是解析 JSON 與 Form URL‑Encoded 請求的核心工具。 - 在 TypeScript 專案中,透過 型別斷言、介面 或 驗證函式庫(如 Zod)能讓
req.body的使用更安全、更具可讀性。 - 正確設定
limit、extended、錯誤中介軟體,並遵守 掛載順序,可以避免常見的「Body 為 undefined」或「JSON 解析失敗」等問題。 - 了解何時應使用 JSON、何時使用 Form URL‑Encoded,配合實務需求(API、Webhook、舊版表單),讓你的 Express 應用既 彈性 又 可靠。
掌握以上概念與實作技巧,你就能在 Express + TypeScript 的開發中,輕鬆處理各式請求資料,為前端與其他服務提供穩定、可預測的 API 介面。祝開發順利 🚀