本文 AI 產出,尚未審核

ExpressJS (TypeScript) – Swagger / OpenAPI 文件化

使用 swagger-ui-express


簡介

在現代的 Web API 開發流程中,文件化 已不再是事後的附加工作,而是必須與程式碼同步演進的核心需求。
透過 OpenAPI (原 Swagger) 規範,我們可以自動產生機器可讀的 API 描述,同時提供一個互動式的 UI 讓前端、測試人員或第三方開發者直接在瀏覽器測試介面。

swagger-ui-express 是官方推薦的 Express 中介軟體,結合 TypeScript 後不僅能保留型別安全,還能在編譯階段即捕捉文件描述的錯誤。
本篇教學將從安裝、基本設定、進階範例一路帶你完成一套可在開發與測試環境即時瀏覽的 API 文件。


核心概念

1. OpenAPI 與 Swagger 的關係

  • OpenAPI:由 Linux 基金會維護的 API 規範標準,描述 API 的路由、參數、回傳格式等。
  • Swagger:最早的實作名稱,現在通常指兩件事:
    1. swagger-ui – 讓 JSON/YAML 規格渲染成互動式網頁。
    2. swagger-jsdoc / swagger-ui-express – 在 Node/Express 中自動產生與掛載 UI 的工具。

2. 為什麼要在 TypeScript 專案使用 swagger-ui-express?

好處 說明
型別安全 使用 @types/swagger-ui-express,IDE 能即時提示設定錯誤。
自動產生 swagger-jsdoc 會根據 JSDoc/TS 註解產出 OpenAPI JSON,減少手寫重複工作。
即時測試 UI 內建「Try it out」功能,直接發送請求驗證路由是否正確。
CI/CD 整合 產出的 JSON 可作為合約測試或 API 版本管理的依據。

3. 基本流程概覽

  1. 安裝相依套件
  2. 建立 OpenAPI 規格檔(JSON 或 YAML)或使用 swagger-jsdoc 動態產生。
  3. 在 Express 中掛載 swagger-ui,設定路徑(如 /api-docs)。
  4. 在程式碼中加入註解,讓 swagger-jsdoc 把路由資訊注入規格檔。
  5. 啟動伺服器,瀏覽器開啟 /api-docs 即可看到文件。

程式碼範例

以下範例採用 TypeScript,並以 express@4swagger-ui-expressswagger-jsdoc 為核心套件。

3.1 安裝必要套件

npm i express swagger-ui-express swagger-jsdoc
npm i -D typescript @types/express @types/swagger-ui-express ts-node-dev

Tipts-node-dev 方便開發時自動重啟。

3.2 建立 src/server.ts

import express, { Request, Response } from 'express';
import swaggerUi from 'swagger-ui-express';
import swaggerJSDoc from 'swagger-jsdoc';
import path from 'path';

// -------------------- 1. 建立 Express App --------------------
const app = express();
app.use(express.json());

// -------------------- 2. 設定 Swagger JSDoc --------------------
const swaggerOptions: swaggerJSDoc.Options = {
  definition: {
    openapi: '3.0.3',
    info: {
      title: 'My Express API',
      version: '1.0.0',
      description: '使用 swagger-ui-express 的範例文件',
    },
    servers: [
      { url: 'http://localhost:3000', description: '本機開發環境' },
    ],
  },
  // 指向所有使用 JSDoc 註解的檔案
  apis: [path.join(__dirname, '/routes/*.ts')],
};

const swaggerSpec = swaggerJSDoc(swaggerOptions);

// -------------------- 3. 掛載 Swagger UI --------------------
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

// -------------------- 4. 載入路由 --------------------
import userRouter from './routes/user';
app.use('/users', userRouter);

// -------------------- 5. 啟動 Server --------------------
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`🚀 Server listening on http://localhost:${PORT}`);
  console.log(`📚 Swagger UI: http://localhost:${PORT}/api-docs`);
});

3.3 建立路由檔 src/routes/user.ts

import { Router, Request, Response } from 'express';
const router = Router();

/**
 * @swagger
 * /users:
 *   get:
 *     summary: 取得使用者列表
 *     tags: [User]
 *     responses:
 *       200:
 *         description: 成功回傳使用者陣列
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/User'
 */
router.get('/', (req: Request, res: Response) => {
  // 假資料
  const users = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob', email: 'bob@example.com' },
  ];
  res.json(users);
});

/**
 * @swagger
 * /users/{id}:
 *   get:
 *     summary: 依 ID 取得單一使用者
 *     tags: [User]
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: integer
 *         description: 使用者唯一識別碼
 *     responses:
 *       200:
 *         description: 使用者資料
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/User'
 *       404:
 *         description: 找不到使用者
 */
router.get('/:id', (req: Request, res: Response) => {
  const userId = Number(req.params.id);
  const user = { id: userId, name: 'Alice', email: 'alice@example.com' };
  if (userId === 1) {
    res.json(user);
  } else {
    res.status(404).json({ message: 'User not found' });
  }
});

/**
 * @swagger
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       required:
 *         - id
 *         - name
 *         - email
 *       properties:
 *         id:
 *           type: integer
 *           description: 使用者編號
 *         name:
 *           type: string
 *           description: 使用者名稱
 *         email:
 *           type: string
 *           format: email
 *           description: 電子郵件
 */
export default router;

3.4 加入全域錯誤處理(可選)

// 放在 server.ts 最後
app.use((err: Error, _: Request, res: Response, __: any) => {
  console.error(err);
  res.status(500).json({ message: 'Internal Server Error' });
});

3.5 使用 npm run devts-node-dev 啟動

// package.json scripts
{
  "scripts": {
    "dev": "ts-node-dev --respawn src/server.ts"
  }
}

執行 npm run dev 後,瀏覽器開啟 http://localhost:3000/api-docs 即可看到 Swagger UI,且「Try it out」功能會直接呼叫上述 /users/users/{id} 兩個端點。


常見陷阱與最佳實踐

陷阱 說明 解決方案
規格與程式碼不一致 手寫 JSON 時容易遺漏參數或回傳格式。 使用 swagger-jsdoc + JSDoc 註解,讓文件自動同步。
路由路徑忘記加 / app.use('/api-docs', ...) 若少了 /,會導致 UI 無法正確載入資源。 檢查路徑前後斜線,或使用 path.join 產生絕對路徑。
Schema 重複定義 多個檔案都寫同樣的 components.schemas,會造成衝突。 把共用 schema 抽離到 src/docs/schemas.ts,在所有檔案中 $ref
開發環境暴露機密 Swagger UI 部署到正式環境前未關閉,可能洩漏內部 API。 process.env.NODE_ENV !== 'production' 時才掛載 UI,或使用環境變數控制。
型別與 Swagger 不匹配 TypeScript 型別是 number,但 Swagger 定義為 string 兩者保持一致,或使用 ts-json-schema-generator 自動產生 schema。

最佳實踐清單

  1. 分層管理/docs 資料夾存放全域 swaggerOptions、共用 schema;路由檔內只寫與該路由相關的註解。
  2. 版本化:在 info.version 加上語意化版本號,並在 URL 中加入 v1v2 等前綴,避免舊版客戶端被破壞。
  3. 自動測試:使用 swagger-cli validateopenapi-validator 在 CI 中驗證產出的 JSON。
  4. 安全性:若需要驗證,於 Swagger UI 加入 Authorization 標頭(使用 swagger-ui-expressswaggerOptions 設定 authAction)。
  5. 文件維護:每次新增或變更 API 時,務必同步更新 JSDoc 註解,讓 PR 檢查更具體。

實際應用場景

場景 需求 使用 swagger-ui-express 的好處
前後端分離開發 前端需要即時了解 API 參數與回傳結構。 UI 提供即時測試,減少跨團隊溝通成本。
微服務間契約測試 多個服務間依賴 OpenAPI 規範。 產出的 JSON 可作為 contract testing(如 Pact)基礎。
第三方合作夥伴 對外提供 API 介面說明。 只要把 /api-docs 部署至公開網址,即可讓合作夥伴直接閱讀與測試。
內部工具或管理平台 管理員需要快速檢視與執行 CRUD 操作。 Swagger UI 本身就提供 CRUD 測試介面,省去額外開發。
自動產生 SDK 需要根據 OpenAPI 產生 TypeScript/JavaScript SDK。 有了正確的 OpenAPI JSON,可使用 openapi-generator-cli 一鍵產生 client 程式庫。

總結

  • swagger-ui-express 結合 swagger-jsdoc 能讓 Express + TypeScript 專案在開發階段即擁有完整、互動式的 API 文件。
  • 透過 JSDoc 註解,文件與程式碼同步,降低錯誤與維護成本。
  • 注意 環境切換(開發 vs 正式)與 schema 共用,才能在安全與可維護性上取得最佳平衡。
  • 最後,將產出的 OpenAPI 規格納入 CI、契約測試或 SDK 生成流程,讓文件成為整個開發生態的一環,而非事後補救的文件。

祝你在使用 swagger-ui-express 為 ExpressJS (TypeScript) 應用打造專業文件的路上,順利又愉快! 🚀