本文 AI 產出,尚未審核

Swagger + TypeScript 整合實務教學


簡介

在現代的前後端分離開發模式中,API 文件化 已成為團隊協作與維護的關鍵。Swagger(亦稱 OpenAPI)提供了一套標準化的規範,讓開發者可以以機器可讀的方式描述 RESTful API,進而自動產生文件、測試介面與程式碼範本。

TypeScript 則以靜態型別優勢,提升 JavaScript 程式的可讀性與可靠性。把兩者結合後,我們不只可以得到即時的 API 文件,還能在編譯階段即捕捉型別錯誤,減少前後端溝通成本與執行時的 bug。

本篇文章將從概念說明、工具選型、到實作範例,完整展示 Swagger + TypeScript 的整合流程,適合剛入門的開發者,也能讓已有經驗的工程師快速落地。


核心概念

1. OpenAPI (Swagger) 基礎

OpenAPI 是一套 JSON/YAML 格式的規範,用來描述 API 的路由、請求參數、回傳結構、驗證規則等。常見的關鍵元素包括:

元素 說明
paths 定義每一個 endpoint(路徑)與 HTTP 方法
components.schemas 宣告資料模型(即 TypeScript 介面)
security 設定認證機制(JWT、OAuth2 等)
tags 用於分組文件與 UI 目錄

Tip:使用 Swagger UIRedoc 可以即時預覽 OpenAPI 文件,對於 API 設計的迭代非常友善。


2. 為何要把 Swagger 產生的型別導入 TypeScript?

  • 型別安全:自動產生的介面保證了前端呼叫 API 時的參數與回傳結構正確。
  • 減少重複工作:不必手動撰寫 DTO(Data Transfer Object),避免與後端規格不一致。
  • 提升開發效率:IDE 能提供自動補全、即時錯誤提示,讓開發者專注於商業邏輯。

3. 常見整合工具

工具 語言 主要功能 特色
openapi-generator-cli Java 產生多種語言的 client/server 程式碼 支援自訂模板、可直接產出 TypeScript Axios client
swagger-codegen Java 與 openapi-generator 前身相同 社群較舊,功能較少
tsoa TypeScript 從 TypeScript 程式碼自動產生 OpenAPI 文件,同時產生路由 讓「先寫程式碼、後產文件」成為可能
@nestjs/swagger TypeScript (NestJS) 在 NestJS 框架內使用裝飾器產生 OpenAPI 與 NestDI 完美結合
swagger-typescript-api Node 只產生 TypeScript 型別與 API 呼叫函式 輕量、無需 Java 環境

以下將以 openapi-generatortsoa@nestjs/swagger 為例,示範實務操作。


程式碼範例

⚠️ 前置作業

  1. 安裝 Node.js 18+npm(或 pnpm, yarn
  2. 若使用 openapi-generator-cli,需要先安裝 Java 11+ 或使用 Docker 版

範例 1:使用 openapi-generator-cli 產生 TypeScript Axios client

# 1. 安裝 generator
npm i -g @openapitools/openapi-generator-cli

# 2. 取得 OpenAPI 規格(假設放在 ./openapi.yaml)
# 3. 產生 client 程式碼
openapi-generator-cli generate \
  -i ./openapi.yaml \
  -g typescript-axios \
  -o ./src/api-client \
  --additional-properties=useSingleRequestParameter=true,withInterfaces=true

產生的目錄結構:

src/
 └─ api-client/
     ├─ api/
     │   └─ UserApi.ts
     ├─ models/
     │   └─ UserDto.ts
     └─ index.ts

使用方式

import { UserApi, UserDto } from '@/api-client';
import axios from 'axios';

// 設定 axios 實例(可加入攔截器、JWT)
const api = new UserApi(undefined, 'https://api.example.com', axios);

// 取得使用者資料
async function getUser(id: string): Promise<UserDto> {
  const response = await api.getUserById({ id });
  return response.data; // TypeScript 已自動推斷為 UserDto
}

重點UserDto 為 generator 直接從 components.schemas.User 轉換而來,型別安全 完整保留。


範例 2:使用 tsoa 由 TypeScript 程式碼產生 OpenAPI 文件

1️⃣ 安裝套件

npm i -D tsoa tsoa-cli
npm i express body-parser

2️⃣ 建立模型與控制器

// src/models/User.ts
export interface User {
  /** 使用者唯一 ID */
  id: string;
  /** 使用者名稱 */
  name: string;
  /** 電子郵件 (optional) */
  email?: string;
}
// src/controllers/UserController.ts
import { Controller, Get, Route, Path, Tags, Response } from 'tsoa';
import { User } from '../models/User';

@Route('users')
@Tags('User')
export class UserController extends Controller {
  /** 取得單一使用者 */
  @Get('{id}')
  @Response<User>(200, '成功')
  async getUser(@Path() id: string): Promise<User> {
    // 假設從資料庫或服務取得
    return { id, name: 'Alice', email: 'alice@example.com' };
  }
}

3️⃣ 設定 tsoa.json

{
  "entryFile": "src/server.ts",
  "noImplicitAdditionalProperties": "throw-on-extras",
  "controllerPathGlobs": ["src/controllers/**/*.ts"],
  "spec": {
    "outputDirectory": "src/swagger",
    "specVersion": 3
  },
  "routes": {
    "basePath": "/api",
    "routesDir": "src/routes"
  }
}

4️⃣ 產生文件與路由

npx tsoa swagger   # 產生 openapi.json
npx tsoa routes    # 產生 Express 路由

5️⃣ 整合 Express

// src/server.ts
import express from 'express';
import bodyParser from 'body-parser';
import swaggerUi from 'swagger-ui-express';
import * as swaggerDocument from './swagger/swagger.json';
import './routes'; // 由 tsoa 產生的路由檔

const app = express();
app.use(bodyParser.json());

// Swagger UI
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

app.listen(3000, () => console.log('Server listening on :3000'));

技巧tsoa程式碼即文件(code‑first)成為可能,避免文件與實作不同步 的常見問題。


範例 3:在 NestJS 中使用 @nestjs/swagger

npm i @nestjs/swagger swagger-ui-express

建立 DTO 與 Controller

// src/users/dto/create-user.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';

export class CreateUserDto {
  @ApiProperty({ description: '使用者名稱' })
  @IsString()
  @IsNotEmpty()
  name: string;

  @ApiProperty({ description: '電子郵件', required: false })
  @IsEmail()
  email?: string;
}
// src/users/users.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { ApiTags, ApiCreatedResponse } from '@nestjs/swagger';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './interfaces/user.interface';

@ApiTags('users')
@Controller('users')
export class UsersController {
  @Post()
  @ApiCreatedResponse({ description: '使用者建立成功', type: User })
  create(@Body() createUserDto: CreateUserDto): User {
    // 假設回傳建立後的使用者物件
    return { id: '123', ...createUserDto };
  }
}

在根模組中啟用 Swagger

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('My API')
    .setDescription('示範 NestJS + Swagger')
    .setVersion('1.0')
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api-docs', app, document);

  await app.listen(3000);
}
bootstrap();

重點@ApiProperty 會自動產生 OpenAPI schema,而 class‑validator 的驗證規則同時被 Swagger 反映,做到 驗證與文件同步


範例 4:手寫型別 + swagger-typescript-api(輕量方案)

npm i -D swagger-typescript-api

產生型別與呼叫函式:

npx swagger-typescript-api -p ./openapi.yaml -o ./src/api -n api.ts

產生的 api.ts 會包括:

export interface UserDto {
  id: string;
  name: string;
  email?: string;
}

/**
 * 取得使用者資料
 * @param params.id 使用者 ID
 */
export const getUser = (params: { id: string }) =>
  request<UserDto>('GET', `/users/${params.id}`);

使用方式:

import { getUser } from '@/api/api';

async function showUser() {
  const user = await getUser({ id: 'abc123' });
  console.log(user.name); // TypeScript 直接提示 name 為 string
}

此方案不依賴任何框架,適合 純前端專案(React / Vue)快速取得型別安全的 API 呼叫。


常見陷阱與最佳實踐

陷阱 說明 解法 / 最佳實踐
規格與程式碼不同步 手動維護 OpenAPI 與實作容易出現差異。 使用 code‑first(tsoa / @nestjs/swagger)或 自動產生(openapi-generator)確保單一來源。
產生的型別過於寬鬆 預設會把 anyobject 產生成 any,失去型別保護。 components.schemas 中明確定義屬性與 required,或在 generator 加上 --additional-properties=enumClassPrefix=Enum 等參數。
重覆的 DTO 前端與後端各自維護相同的介面,易產生衝突。 直接把 OpenAPI 作為共同契約,前端使用 generator 產生,後端使用相同規格。
認證資訊未同步 Swagger UI 只能測試無認證的端點。 DocumentBuilderopenapi.yaml 中加入 securitySchemes,並在 UI 設定 Authorize
大型規格導致編譯緩慢 數千個 schema 會讓 TypeScript 編譯變慢。 使用 分割 schema$ref)或 partial generation(只產生需要的 client)。

最佳實踐總結

  1. 單一來源:選擇 code‑firstspec‑first,但切勿兩者同時手寫。
  2. 自動化 CI:在 CI pipeline 中加入 openapi-generatortsoa swagger,確保每次 PR 都產出最新文件。
  3. 型別檢查:在前端使用 eslint-plugin-import 以及 tsc --noEmit,確保產生的型別不會因升級破壞。
  4. 版本管理:將 openapi.yaml 放入 Git,使用 semver 標註 API 變更,避免破壞相依服務。

實際應用場景

場景 為什麼需要 Swagger + TypeScript 推薦方案
多前端團隊共享同一套 API 前端必須在編譯階段即知道回傳型別,避免跑到測試才發現錯誤。 openapi-generator 產生 Axios client,配合 monorepo
微服務間的 RPC / HTTP 通訊 每個服務都有自己的 OpenAPI,透過型別安全的 client 呼叫其他服務。 swagger-typescript-api 輕量化產生,或 NestJS 中的 HttpService + generated DTO。
快速原型與文件同步 需求變更頻繁,需要即時更新文件與測試介面。 tsoacode‑first,只改 controller 就同步更新 Swagger UI。
企業級安全與認證 必須在文件中明確標示 JWT、OAuth2,且前端需要自動帶入 token。 @nestjs/swagger + addBearerAuth(),配合 Axios interceptor
前端 SDK 發佈 想把 API 包裝成 npm 套件供其他團隊使用。 使用 openapi-generator 產出 npm package,發布至私有 registry。

總結

  • Swagger (OpenAPI) 為 API 規格提供標準化、機器可讀的描述,結合 TypeScript 可在編譯階段捕捉型別錯誤,提升開發效率與程式品質。
  • 依照專案需求,可選擇 code‑first(tsoa、@nestjs/swagger)或 spec‑first(openapi-generator、swagger-typescript-api)兩大路線。
  • 在整合過程中,務必避免規格與實作不同步的陷阱,並透過 CI、版本管理與安全設定,將 API 文件化、型別化、測試化納入開發流程。

掌握了上述概念與實作範例後,你就能在 TypeScript 生態 中,輕鬆建立、維護、使用 Swagger 文件,讓前後端合作更加順暢、可靠。祝開發順利! 🚀