# 

# 01-6. 自動生成 API 文件

&gt; ⏱️ **閱讀時間：** 10 分鐘
&gt; 🎯 **難度：** ⭐ (入門)

---

## 🤔 一句話解釋

**FastAPI 自動根據你的程式碼生成互動式 API 文件，不需要額外撰寫！**

---

## 📚 兩種內建文件介面

FastAPI 內建兩種 API 文件介面：

### Swagger UI

```
訪問: http://localhost:8000/docs
```

**特色：**
- ✅ 互動式測試（直接在瀏覽器發送請求）
- ✅ 清楚的請求/回應格式
- ✅ 支援認證測試
- ✅ 業界標準

### ReDoc

```
訪問: http://localhost:8000/redoc
```

**特色：**
- ✅ 更美觀的閱讀介面
- ✅ 適合作為對外文件
- ✅ 支援搜尋
- ✅ 支援下載 OpenAPI 規格

---

## 🎨 自訂 API 資訊

### 基本設定

```python
from fastapi import FastAPI

app = FastAPI(
    title=&#34;我的超棒 API&#34;,
    description=&#34;&#34;&#34;
## 🚀 API 說明

這是一個功能強大的 API，提供以下功能：

* **使用者管理** - 註冊、登入、個人資料
* **商品管理** - 新增、編輯、刪除商品
* **訂單處理** - 建立訂單、追蹤狀態

### 認證方式

使用 JWT Bearer Token 進行認證。
    &#34;&#34;&#34;,
    version=&#34;1.0.0&#34;,
    terms_of_service=&#34;https://example.com/terms/&#34;,
    contact={
        &#34;name&#34;: &#34;API Support&#34;,
        &#34;url&#34;: &#34;https://example.com/support&#34;,
        &#34;email&#34;: &#34;support@example.com&#34;,
    },
    license_info={
        &#34;name&#34;: &#34;MIT License&#34;,
        &#34;url&#34;: &#34;https://opensource.org/licenses/MIT&#34;,
    },
)
```

### 自訂文件路徑

```python
app = FastAPI(
    docs_url=&#34;/api/docs&#34;,        # Swagger UI 路徑
    redoc_url=&#34;/api/redoc&#34;,      # ReDoc 路徑
    openapi_url=&#34;/api/openapi.json&#34;,  # OpenAPI schema 路徑
)

# 或者完全禁用（私有 API）
app = FastAPI(
    docs_url=None,
    redoc_url=None,
    openapi_url=None,
)
```

---

## 🏷️ 使用 Tags 組織 API

### 基本用法

```python
from fastapi import FastAPI

app = FastAPI()

# 使用 tags 分類 API
@app.get(&#34;/users/&#34;, tags=[&#34;使用者&#34;])
async def list_users():
    &#34;&#34;&#34;列出所有使用者&#34;&#34;&#34;
    return []

@app.post(&#34;/users/&#34;, tags=[&#34;使用者&#34;])
async def create_user():
    &#34;&#34;&#34;建立新使用者&#34;&#34;&#34;
    return {}

@app.get(&#34;/items/&#34;, tags=[&#34;商品&#34;])
async def list_items():
    &#34;&#34;&#34;列出所有商品&#34;&#34;&#34;
    return []

@app.post(&#34;/items/&#34;, tags=[&#34;商品&#34;])
async def create_item():
    &#34;&#34;&#34;建立新商品&#34;&#34;&#34;
    return {}
```

### 定義 Tags 詳細資訊

```python
from fastapi import FastAPI

tags_metadata = [
    {
        &#34;name&#34;: &#34;使用者&#34;,
        &#34;description&#34;: &#34;使用者相關操作，包含註冊、登入、個人資料管理&#34;,
    },
    {
        &#34;name&#34;: &#34;商品&#34;,
        &#34;description&#34;: &#34;商品相關操作&#34;,
        &#34;externalDocs&#34;: {
            &#34;description&#34;: &#34;商品 API 詳細說明&#34;,
            &#34;url&#34;: &#34;https://example.com/docs/items&#34;,
        },
    },
    {
        &#34;name&#34;: &#34;訂單&#34;,
        &#34;description&#34;: &#34;訂單相關操作&#34;,
    },
]

app = FastAPI(
    title=&#34;電商 API&#34;,
    openapi_tags=tags_metadata
)
```

---

## 📝 撰寫好的 API 文件

### Docstring 會自動出現在文件中

```python
from fastapi import FastAPI, Path, Query
from pydantic import BaseModel, Field

app = FastAPI()

class UserCreate(BaseModel):
    &#34;&#34;&#34;建立使用者的請求模型&#34;&#34;&#34;
    name: str = Field(..., description=&#34;使用者名稱&#34;, example=&#34;John Doe&#34;)
    email: str = Field(..., description=&#34;電子郵件&#34;, example=&#34;john@example.com&#34;)
    age: int = Field(..., ge=0, le=150, description=&#34;年齡&#34;, example=25)

class UserResponse(BaseModel):
    &#34;&#34;&#34;使用者回應模型&#34;&#34;&#34;
    id: int = Field(..., description=&#34;使用者 ID&#34;, example=1)
    name: str = Field(..., description=&#34;使用者名稱&#34;, example=&#34;John Doe&#34;)
    email: str = Field(..., description=&#34;電子郵件&#34;, example=&#34;john@example.com&#34;)

@app.post(
    &#34;/users/&#34;,
    response_model=UserResponse,
    tags=[&#34;使用者&#34;],
    summary=&#34;建立新使用者&#34;,
    response_description=&#34;成功建立的使用者資訊&#34;
)
async def create_user(user: UserCreate):
    &#34;&#34;&#34;
    建立新使用者帳號

    - **name**: 使用者的顯示名稱，長度 1-100 字元
    - **email**: 唯一的電子郵件地址，會用於登入
    - **age**: 使用者年齡，必須介於 0-150 之間

    成功建立後會回傳使用者資訊（不包含密碼）。

    ### 注意事項

    1. Email 必須是唯一的
    2. 建立後會發送驗證郵件
    &#34;&#34;&#34;
    return {&#34;id&#34;: 1, &#34;name&#34;: user.name, &#34;email&#34;: user.email}
```

### 路徑參數和查詢參數的文件

```python
from fastapi import FastAPI, Path, Query
from typing import Optional
from enum import Enum

app = FastAPI()

class SortOrder(str, Enum):
    asc = &#34;asc&#34;
    desc = &#34;desc&#34;

@app.get(&#34;/users/{user_id}&#34;, tags=[&#34;使用者&#34;])
async def get_user(
    user_id: int = Path(
        ...,
        title=&#34;使用者 ID&#34;,
        description=&#34;要查詢的使用者 ID&#34;,
        ge=1,
        example=123
    ),
    include_posts: bool = Query(
        False,
        description=&#34;是否包含使用者的文章&#34;
    ),
    include_comments: bool = Query(
        False,
        description=&#34;是否包含使用者的評論&#34;
    )
):
    &#34;&#34;&#34;
    根據 ID 獲取使用者資訊

    可選擇是否包含相關的文章和評論資料。
    &#34;&#34;&#34;
    return {&#34;user_id&#34;: user_id}

@app.get(&#34;/users/&#34;, tags=[&#34;使用者&#34;])
async def list_users(
    skip: int = Query(0, ge=0, description=&#34;跳過的數量&#34;),
    limit: int = Query(10, ge=1, le=100, description=&#34;返回的數量&#34;),
    search: Optional[str] = Query(
        None,
        min_length=2,
        max_length=50,
        description=&#34;搜尋關鍵字（姓名或 email）&#34;
    ),
    sort_by: str = Query(&#34;created_at&#34;, description=&#34;排序欄位&#34;),
    order: SortOrder = Query(SortOrder.desc, description=&#34;排序方向&#34;)
):
    &#34;&#34;&#34;
    列出使用者清單

    支援分頁、搜尋和排序功能。
    &#34;&#34;&#34;
    return []
```

---

## 🎯 定義回應範例

### 方法 1：在 Field 中使用 example

```python
from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str = Field(..., example=&#34;iPhone 15&#34;)
    price: float = Field(..., example=999.99)
    description: str = Field(None, example=&#34;最新款智慧型手機&#34;)
```

### 方法 2：使用 model_config

```python
from pydantic import BaseModel, ConfigDict

class Item(BaseModel):
    name: str
    price: float
    description: str = None

    model_config = ConfigDict(
        json_schema_extra={
            &#34;example&#34;: {
                &#34;name&#34;: &#34;iPhone 15&#34;,
                &#34;price&#34;: 999.99,
                &#34;description&#34;: &#34;最新款智慧型手機&#34;
            }
        }
    )
```

### 方法 3：在路由中定義多個範例

```python
from fastapi import FastAPI, Body

app = FastAPI()

@app.post(&#34;/items/&#34;)
async def create_item(
    item: Item = Body(
        ...,
        openapi_examples={
            &#34;normal&#34;: {
                &#34;summary&#34;: &#34;一般商品&#34;,
                &#34;description&#34;: &#34;一般商品的範例&#34;,
                &#34;value&#34;: {
                    &#34;name&#34;: &#34;筆記本&#34;,
                    &#34;price&#34;: 99.9,
                    &#34;description&#34;: &#34;A5 大小&#34;
                }
            },
            &#34;expensive&#34;: {
                &#34;summary&#34;: &#34;高價商品&#34;,
                &#34;description&#34;: &#34;高價商品的範例&#34;,
                &#34;value&#34;: {
                    &#34;name&#34;: &#34;MacBook Pro&#34;,
                    &#34;price&#34;: 59900.0,
                    &#34;description&#34;: &#34;16 吋 M3 Max&#34;
                }
            }
        }
    )
):
    return item
```

---

## 📊 定義多種回應

```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

class Message(BaseModel):
    message: str

@app.get(
    &#34;/items/{item_id}&#34;,
    response_model=Item,
    responses={
        200: {
            &#34;description&#34;: &#34;成功獲取商品&#34;,
            &#34;content&#34;: {
                &#34;application/json&#34;: {
                    &#34;example&#34;: {&#34;name&#34;: &#34;iPhone&#34;, &#34;price&#34;: 999.99}
                }
            }
        },
        404: {
            &#34;description&#34;: &#34;商品不存在&#34;,
            &#34;model&#34;: Message,
            &#34;content&#34;: {
                &#34;application/json&#34;: {
                    &#34;example&#34;: {&#34;message&#34;: &#34;商品不存在&#34;}
                }
            }
        },
        422: {
            &#34;description&#34;: &#34;驗證錯誤&#34;
        }
    }
)
async def get_item(item_id: int):
    if item_id == 0:
        raise HTTPException(status_code=404, detail=&#34;商品不存在&#34;)
    return {&#34;name&#34;: &#34;Item&#34;, &#34;price&#34;: 10.0}
```

---

## 🔐 在文件中添加認證

```python
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

app = FastAPI()

security = HTTPBearer()

@app.get(&#34;/protected&#34;, tags=[&#34;受保護的端點&#34;])
async def protected_route(
    credentials: HTTPAuthorizationCredentials = Depends(security)
):
    &#34;&#34;&#34;
    這是一個需要認證的端點

    需要在 Header 中提供 Bearer Token：
    ```
    Authorization: Bearer &lt;your_token&gt;
    ```
    &#34;&#34;&#34;
    return {&#34;token&#34;: credentials.credentials}
```

**Swagger UI 會自動顯示「Authorize」按鈕！**

---

## 📤 匯出 OpenAPI 規格

```python
import json
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

app = FastAPI()

@app.get(&#34;/items/&#34;)
async def list_items():
    return []

# 獲取 OpenAPI schema
def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title=&#34;自訂 API&#34;,
        version=&#34;1.0.0&#34;,
        description=&#34;這是自訂的 API 描述&#34;,
        routes=app.routes,
    )

    # 可以在這裡自訂 schema
    openapi_schema[&#34;info&#34;][&#34;x-logo&#34;] = {
        &#34;url&#34;: &#34;https://example.com/logo.png&#34;
    }

    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

# 匯出為 JSON 檔案
if __name__ == &#34;__main__&#34;:
    with open(&#34;openapi.json&#34;, &#34;w&#34;) as f:
        json.dump(app.openapi(), f, indent=2)
```

---

## ✅ 重點總結

### API 文件最佳實踐

```python
# 1. 設定 API 基本資訊
app = FastAPI(
    title=&#34;我的 API&#34;,
    description=&#34;詳細說明...&#34;,
    version=&#34;1.0.0&#34;
)

# 2. 使用 Tags 分類
@app.get(&#34;/users&#34;, tags=[&#34;使用者&#34;])

# 3. 撰寫清楚的 docstring
async def create_user(user: UserCreate):
    &#34;&#34;&#34;
    建立新使用者

    - **name**: 使用者名稱
    - **email**: 電子郵件
    &#34;&#34;&#34;

# 4. 在 Pydantic 模型使用 Field
class User(BaseModel):
    name: str = Field(..., description=&#34;名稱&#34;, example=&#34;John&#34;)

# 5. 定義多種回應狀態
@app.get(&#34;/items/{id}&#34;, responses={404: {&#34;model&#34;: ErrorResponse}})
```

### 文件網址

| 網址 | 說明 |
|------|------|
| `/docs` | Swagger UI（互動式測試） |
| `/redoc` | ReDoc（閱讀友善） |
| `/openapi.json` | OpenAPI 規格（JSON） |

---

## 🎤 面試這樣答

### Q: FastAPI 如何自動生成 API 文件？

**答案：**

&gt; FastAPI 會自動根據程式碼中的 Type Hints、Pydantic 模型和 Docstring 生成 OpenAPI 規格（也叫 Swagger 規格）。
&gt;
&gt; 這包括：
&gt; - 路由資訊（路徑、方法）
&gt; - 參數定義（路徑參數、查詢參數、請求體）
&gt; - 回應格式（從 response_model 推斷）
&gt; - 驗證規則（從 Pydantic 的 Field 推斷）
&gt;
&gt; 然後內建的 Swagger UI 和 ReDoc 會渲染這個規格，提供互動式文件。

---

## 🤓 小測驗

1. Swagger UI 的預設路徑是什麼？
   &lt;details&gt;
   &lt;summary&gt;點擊看答案&lt;/summary&gt;
   /docs
   &lt;/details&gt;

2. 如何在文件中將 API 分組？
   &lt;details&gt;
   &lt;summary&gt;點擊看答案&lt;/summary&gt;
   使用 tags 參數，例如 @app.get(&#34;/users&#34;, tags=[&#34;使用者&#34;])
   &lt;/details&gt;

3. Pydantic 模型中如何添加欄位說明？
   &lt;details&gt;
   &lt;summary&gt;點擊看答案&lt;/summary&gt;
   使用 Field，例如 name: str = Field(..., description=&#34;使用者名稱&#34;)
   &lt;/details&gt;

---

**上一篇：** [01-5. 狀態碼與錯誤處理](./01-5)
**下一篇：** [01-7. 專案結構最佳實踐](./01-7)

---

最後更新：2025-12-17


---

> 作者: luk  
> URL: https://yoru-karu-blog-lalaluk-52581ac5e0cef170a3c8922c19182ecb6f7bd604.gitlab.io/posts/tutorial/fastapi/01-6/  

