API 文件與 OpenAPI:Swagger 實戰
好的 API 需要好的文件。OpenAPI(原 Swagger)是 API 文件的業界標準。本篇將介紹如何使用它。
一、 什麼是 OpenAPI?
1.1 概念
OpenAPI Specification(OAS)是一種 API 描述格式:
- 標準化的 API 文件格式
- 可被機器讀取
- 可自動生成互動式文件
- 可生成客戶端 SDK(Software Development Kit,軟體開發套件)
1.2 生態系統
二、 OpenAPI 規範
2.1 基本結構
yaml
openapi: 3.0.0
info:
title: My API
version: 1.0.0
description: A sample API
servers:
- url: https://api.example.com/v1
description: Production
- url: http://localhost:3000/v1
description: Development
paths:
/users:
# endpoints...
components:
schemas:
# data models...
securitySchemes:
# auth methods...2.2 定義 Path
yaml
paths:
/users:
get:
summary: List all users
description: Returns a list of users
tags:
- Users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
responses:
200:
description: Success
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/User"
pagination:
$ref: "#/components/schemas/Pagination"
401:
$ref: "#/components/responses/Unauthorized"
post:
summary: Create a user
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUser"
responses:
201:
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
/users/{id}:
get:
summary: Get a user
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
200:
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/User"
404:
$ref: "#/components/responses/NotFound"2.3 定義 Schema
yaml
components:
schemas:
User:
type: object
required:
- id
- name
- email
properties:
id:
type: string
example: "123"
name:
type: string
example: "John Doe"
email:
type: string
format: email
example: "john@example.com"
createdAt:
type: string
format: date-time
example: "2025-01-13T00:00:00Z"
CreateUser:
type: object
required:
- name
- email
properties:
name:
type: string
minLength: 1
maxLength: 100
email:
type: string
format: email
password:
type: string
minLength: 8
Pagination:
type: object
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
totalPages:
type: integer
Error:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string2.4 定義認證
yaml
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
apiKey:
type: apiKey
in: header
name: X-API-Key
security:
- bearerAuth: []
# 針對特定端點覆蓋
paths:
/public:
get:
security: [] # 不需要認證三、 Express 整合
3.1 swagger-jsdoc
從程式碼註解生成文件:
javascript
const swaggerJsdoc = require("swagger-jsdoc");
const swaggerUi = require("swagger-ui-express");
const options = {
definition: {
openapi: "3.0.0",
info: {
title: "My API",
version: "1.0.0",
},
servers: [{ url: "http://localhost:3000/api" }],
},
apis: ["./routes/*.js"],
};
const spec = swaggerJsdoc(options);
app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec));3.2 JSDoc 註解
javascript
/**
* @openapi
* /users:
* get:
* summary: List users
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* responses:
* 200:
* description: Success
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
router.get("/", async (req, res) => {
// ...
});
/**
* @openapi
* components:
* schemas:
* User:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
*/3.3 使用 YAML 檔案
javascript
const YAML = require("yamljs");
const swaggerDocument = YAML.load("./openapi.yaml");
app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));四、 互動式文件
4.1 Swagger UI
javascript
const swaggerUi = require("swagger-ui-express");
app.use(
"/docs",
swaggerUi.serve,
swaggerUi.setup(spec, {
customCss: ".swagger-ui .topbar { display: none }",
customSiteTitle: "My API Docs",
swaggerOptions: {
persistAuthorization: true,
},
})
);4.2 ReDoc
javascript
const redoc = require("redoc-express");
app.get(
"/docs",
redoc({
title: "My API",
specUrl: "/openapi.json",
})
);
app.get("/openapi.json", (req, res) => {
res.json(spec);
});五、 程式碼生成
5.1 生成客戶端
bash
# 安裝 OpenAPI Generator
npm install @openapitools/openapi-generator-cli -g
# 生成 TypeScript 客戶端
openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./client5.2 使用生成的客戶端
typescript
import { UsersApi, Configuration } from "./client";
const config = new Configuration({
basePath: "https://api.example.com",
accessToken: "your-token",
});
const usersApi = new UsersApi(config);
// 完全類型安全
const users = await usersApi.getUsers({ page: 1, limit: 20 });六、 契約測試
6.1 驗證回應符合規範
javascript
const OpenAPIValidator = require("express-openapi-validator");
app.use(
OpenAPIValidator.middleware({
apiSpec: "./openapi.yaml",
validateRequests: true,
validateResponses: true,
})
);
// 錯誤處理
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
error: {
code: "VALIDATION_ERROR",
message: err.message,
errors: err.errors,
},
});
});6.2 測試時驗證
javascript
const { OpenAPISchemaValidator } = require("openapi-schema-validator");
describe("API", () => {
it("should match OpenAPI spec", async () => {
const response = await request(app).get("/users");
const validator = new OpenAPISchemaValidator({ version: 3 });
const result = validator.validate(spec);
expect(result.errors).toHaveLength(0);
});
});七、 最佳實踐
7.1 組織結構
/docs
/openapi
openapi.yaml # 主文件
/paths
users.yaml
products.yaml
/schemas
user.yaml
product.yaml
/responses
errors.yaml7.2 使用 $ref 分割
yaml
# openapi.yaml
paths:
/users:
$ref: "./paths/users.yaml"
components:
schemas:
User:
$ref: "./schemas/user.yaml"7.3 版本控制
yaml
# v1/openapi.yaml
info:
version: 1.0.0
# v2/openapi.yaml
info:
version: 2.0.0總結
| 工具 | 用途 |
|---|---|
| OpenAPI Spec | API 描述語言 |
| Swagger UI | 互動式文件 |
| swagger-jsdoc | 從註解生成 |
| OpenAPI Generator | 生成客戶端 |
| express-openapi-validator | 契約驗證 |
> **文件即程式碼**——將 OpenAPI 納入版本控制,讓文件與程式碼同步更新。
進階挑戰
- 為你的 API 完整撰寫 OpenAPI 規範。
- 設置 CI/CD(Continuous Integration/Continuous Deployment,持續整合/持續部署)在合併前驗證 API 符合規範。
- 研究 AsyncAPI,了解如何描述事件驅動的 API。