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 UI 或 Redoc 可以即時預覽 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-generator、tsoa、@nestjs/swagger 為例,示範實務操作。
程式碼範例
⚠️ 前置作業
- 安裝 Node.js 18+ 與 npm(或 pnpm, yarn)
- 若使用
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)確保單一來源。 |
| 產生的型別過於寬鬆 | 預設會把 any、object 產生成 any,失去型別保護。 |
在 components.schemas 中明確定義屬性與 required,或在 generator 加上 --additional-properties=enumClassPrefix=Enum 等參數。 |
| 重覆的 DTO | 前端與後端各自維護相同的介面,易產生衝突。 | 直接把 OpenAPI 作為共同契約,前端使用 generator 產生,後端使用相同規格。 |
| 認證資訊未同步 | Swagger UI 只能測試無認證的端點。 | 在 DocumentBuilder 或 openapi.yaml 中加入 securitySchemes,並在 UI 設定 Authorize。 |
| 大型規格導致編譯緩慢 | 數千個 schema 會讓 TypeScript 編譯變慢。 | 使用 分割 schema($ref)或 partial generation(只產生需要的 client)。 |
最佳實踐總結:
- 單一來源:選擇 code‑first 或 spec‑first,但切勿兩者同時手寫。
- 自動化 CI:在 CI pipeline 中加入
openapi-generator或tsoa swagger,確保每次 PR 都產出最新文件。 - 型別檢查:在前端使用
eslint-plugin-import以及tsc --noEmit,確保產生的型別不會因升級破壞。 - 版本管理:將
openapi.yaml放入 Git,使用 semver 標註 API 變更,避免破壞相依服務。
實際應用場景
| 場景 | 為什麼需要 Swagger + TypeScript | 推薦方案 |
|---|---|---|
| 多前端團隊共享同一套 API | 前端必須在編譯階段即知道回傳型別,避免跑到測試才發現錯誤。 | openapi-generator 產生 Axios client,配合 monorepo。 |
| 微服務間的 RPC / HTTP 通訊 | 每個服務都有自己的 OpenAPI,透過型別安全的 client 呼叫其他服務。 | swagger-typescript-api 輕量化產生,或 NestJS 中的 HttpService + generated DTO。 |
| 快速原型與文件同步 | 需求變更頻繁,需要即時更新文件與測試介面。 | tsoa 的 code‑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 文件,讓前後端合作更加順暢、可靠。祝開發順利! 🚀