本文 AI 產出,尚未審核

ExpressJS (TypeScript) – Request 與 Response 操作:設定 Headers


簡介

在 Web 開發中,HTTP Header 是客戶端與伺服器之間傳遞額外資訊的關鍵橋樑。
無論是告訴瀏覽器回傳資料的類型、設定快取策略,或是實作跨來源資源共享(CORS),正確且安全地操作 Header 都是打造高效 API 的基礎。

本篇文章以 ExpressJS + TypeScript 為例,說明在 RequestResponse 物件上如何設定、讀取與管理 Header,提供實務範例、常見陷阱與最佳實踐,幫助從初學者到中級開發者快速上手並寫出更安全、可維護的程式碼。


核心概念

1. Header 的基本特性

特性 說明
大小寫不敏感 Content-Typecontent-typeCONTENT-TYPE 都是同一個欄位。
可重複 同名 Header 可以出現多次(如 Set-Cookie),Express 會把它們合併為陣列。
傳輸限制 Header 的總長度受瀏覽器與伺服器限制(通常在 8KB 左右),過長會導致 431 Request Header Fields Too Large 錯誤。

2. Express 中的 Header API

方法 位置 說明
req.get(name) / req.header(name) Request 取得單一 Header 的值(不區分大小寫)。
req.headers Request 取得所有 Header 的物件(型別為 IncomingHttpHeaders)。
res.set(name, value) / res.header(name, value) Response 設定或覆寫 Header。
res.append(name, value) Response 在同名 Header 後追加值(常用於 Set-Cookie)。
res.type(mime) Response 快速設定 Content-Type(如 res.type('json'))。
res.removeHeader(name) Response 移除已設定的 Header(Node 原生 API)。

Tip:在 TypeScript 中,req.get 會回傳 string | undefined,建議使用空值合併(??)或型別斷言。

3. 為什麼要在 TypeScript 中明確宣告 Header?

使用 TypeScript 可以讓我們:

  • 避免拼字錯誤res.set('Conten-Type', ...) 會在編譯期提示 Argument of type '"Conten-Type"' is not assignable...
  • 取得自動補全:IDE 會根據 http 標準提供常見 Header 列表。
  • 確保值的型別:例如 Cache-Control 必須是字串,res.set('Cache-Control', 3600) 會直接報錯。

程式碼範例

以下範例均以 Express 5.x + TypeScript 為前提,假設已安裝 express@types/expresstypescript,並在 src/app.ts 中編寫。

1️⃣ 基本的 Response Header 設定

// src/app.ts
import express, { Request, Response } from 'express';
const app = express();

app.get('/hello', (req: Request, res: Response) => {
  // 設定 Content-Type 為 JSON
  res.type('json');               // 等同於 res.set('Content-Type', 'application/json')
  // 設定自訂 Header
  res.set('X-Powered-By', 'ExpressTS'); // 建議使用自訂前綴避免與標準 Header 混淆
  // 回傳資料
  res.send({ message: 'Hello, World!' });
});

說明res.type('json') 會自動把 MIME type 設為 application/json; charset=utf-8,省去手寫字串的麻煩。


2️⃣ 讀取 Request Header 並依條件回應

app.get('/auth', (req: Request, res: Response) => {
  // 從 Authorization Header 取得 Bearer Token
  const authHeader = req.get('Authorization'); // 可能回傳 undefined
  const token = authHeader?.split(' ')[1];

  if (!token) {
    // 若沒有 token,回傳 401 並設定 WWW-Authenticate
    res.set('WWW-Authenticate', 'Bearer realm="Access to the protected resource"');
    return res.status(401).send('Missing token');
  }

  // 假設驗證成功
  res.send(`Your token is ${token}`);
});

技巧:使用 ?. (optional chaining) 可以安全取得 token,避免 nullundefined 的錯誤。


3️⃣ 設定快取相關 Header(Cache-Control、ETag)

app.get('/static/:file', (req: Request, res: Response) => {
  const { file } = req.params;
  // 假設檔案位於 public 資料夾
  const filePath = `${__dirname}/public/${file}`;

  // 設定快取 1 小時
  res.set('Cache-Control', 'public, max-age=3600');
  // 設定 ETag(Express 會自動產生,但示範手動設定)
  const etag = `"${Date.now().toString(16)}-${file.length}"`;
  res.set('ETag', etag);

  // 若客戶端送來 If-None-Match,直接回 304
  if (req.get('If-None-Match') === etag) {
    return res.status(304).end();
  }

  res.sendFile(filePath);
});

重點Cache-Control 能大幅減少伺服器負載;ETag 搭配 If-None-Match 能讓瀏覽器只在內容變更時才重新下載。


4️⃣ 使用 res.append 設定多個 Set-Cookie

app.post('/login', (req: Request, res: Response) => {
  // 假設驗證成功後產生 JWT
  const accessToken = 'jwt-access-token';
  const refreshToken = 'jwt-refresh-token';

  // 使用 append 讓 Set-Cookie 可以同時出現多次
  res.append('Set-Cookie', `access=${accessToken}; HttpOnly; Path=/; Max-Age=3600`);
  res.append('Set-Cookie', `refresh=${refreshToken}; HttpOnly; Path=/refresh; Max-Age=604800`);

  res.send({ status: 'ok' });
});

說明:若直接使用 res.set('Set-Cookie', ...),後一次會覆寫前一次。append 能保留多個 cookie。


5️⃣ CORS 與安全 Header(使用 corshelmet

import cors from 'cors';
import helmet from 'helmet';

// 只允許特定前端來源
const corsOptions: cors.CorsOptions = {
  origin: ['https://myfrontend.com', 'https://admin.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true, // 允許傳送 Cookie
};

app.use(cors(corsOptions));   // 自動設定 Access-Control-* Header
app.use(helmet());            // 設定 X-Content-Type-Options、X-Frame-Options 等安全 Header

app.get('/data', (req: Request, res: Response) => {
  res.json({ secret: 'only for allowed origins' });
});

最佳實踐絕不要在正式環境使用 origin: '*',這會讓任意網站都能讀取你的 API,容易造成資料外洩。


常見陷阱與最佳實踐

陷阱 影響 解決方案 / 最佳實踐
重複設定同名 Header 後一次會覆寫前一次,導致 Set-CookieCache-Control 等失效。 使用 res.append 或先取得舊值再合併 (res.get('Header')?.split(', ')).
大小寫不一致 雖然大小寫不敏感,但在程式碼中混用會降低可讀性。 統一使用 Pascal-Case(如 Content-Type)或全小寫,並在 IDE 中啟用自動補全。
過長的 Header 超過伺服器或瀏覽器限制會回傳 431 錯誤。 盡量簡潔,將大量資料搬到 Body(如 JSON)或使用壓縮。
安全資訊外洩 X-Powered-ByServer 等標頭會透露技術棧。 使用 helmet.hidePoweredBy() 或自行移除 (app.disable('x-powered-by'))。
CORS 設定過寬 Access-Control-Allow-Origin: * 讓任何網站都能呼叫 API。 只允許可信任的 origin,並在生產環境使用白名單。
未正確處理預檢請求 (OPTIONS) 前端發出跨域請求時會先送 OPTIONS,若未回應會導致請求失敗。 使用 cors() 中介軟體自動回應,或自行在路由中處理 app.options('*', cors(corsOptions))

其他實用技巧

  • 使用型別別名type HeaderMap = Record<string, string | number | string[]>;,讓自訂 Header 的傳遞更安全。
  • 集中管理 Header 常數:在 src/constants/headers.ts 中定義 export const HEADER_X_REQUEST_ID = 'X-Request-Id';,避免硬編碼。
  • 在測試時驗證 Header:使用 supertest 搭配 expect('Header-Name', /value/) 確保回傳正確。

實際應用場景

  1. API 版本管理
    在回應中加入 API-VersionX-API-Version,讓前端可以根據版本做相容性處理。

  2. 分頁與排序資訊
    透過 X-Total-CountLink Header(RFC 5988)傳遞分頁總筆數與前後頁 URL,減少 body 內的冗餘資料。

  3. 檔案下載與內容處理
    使用 Content-Disposition: attachment; filename="report.pdf" 指示瀏覽器下載檔案,搭配 Content-Type 設定正確 MIME。

  4. 服務間追蹤 (Tracing)
    在微服務架構下,將 X-Request-IdX-Trace-Id 於每次請求傳遞,方便在日誌與 APM 系統中串聯。

  5. 安全防護

    • Strict-Transport-Security(HSTS)強制 HTTPS。
    • X-Content-Type-Options: nosniff 防止瀏覽器 MIME Sniffing。
    • X-Frame-Options: DENY 防止 Clickjacking。

總結

設定 HTTP Header 看似簡單,卻是 Web API 安全、效能與可維護性 的根本。透過 ExpressJS + TypeScript,我們可以:

  • 安全地讀寫 Header:利用 req.getres.setres.append 等 API,配合 TypeScript 型別保證正確性。
  • 掌握快取與跨域:使用 Cache-ControlETagcorshelmet 等標頭,提升使用者體驗與資安防護。
  • 避免常見坑:統一命名、避免重複、控制長度、嚴格 CORS 白名單,讓應用更穩定。

只要把 Header 的設計 當成 API 合約的一部份,並遵循上述最佳實踐,就能寫出 高效、可追蹤、且安全 的 Express + TypeScript 伺服器。祝開發順利,Happy Coding! 🚀