FastAPI 路由(Routing)基礎教學
簡介
在 FastAPI 中,路由(Routing)是將 HTTP 請求對應到程式碼的核心機制。沒有正確的路由設定,前端的請求就找不到後端的處理函式,整個 API 服務將無法運作。
本單元將說明 @app.get、@app.post、@app.put、@app.delete 等裝飾器的使用方式,讓讀者能快速上手建立 RESTful API,並掌握路由的靈活寫法與常見坑洞。
為什麼要先學路由?
- 它決定了 URL 結構 與 HTTP 方法 的對應關係。
- 正確的路由設計能提升 API 的可讀性、可維護性與安全性。
- FastAPI 內建的路由系統結合 型別提示,讓自動產生的 OpenAPI 文件更加完整。
核心概念
1. FastAPI 應用程式與路由裝飾器
在 FastAPI 中,FastAPI() 會產生一個應用程式實例(app),之後所有的路由都會以 裝飾器(Decorator)的形式掛在 app 上。常見的 HTTP 方法對應如下:
| 方法 | 裝飾器 | 說明 |
|---|---|---|
| GET | @app.get() |
用於取得資源(讀取) |
| POST | @app.post() |
用於建立新資源(新增) |
| PUT | @app.put() |
用於完整取代資源(更新) |
| PATCH | @app.patch() |
用於部分更新資源 |
| DELETE | @app.delete() |
用於刪除資源 |
Tip:裝飾器的第一個參數是 路徑(path),可以使用 路徑參數(Path Parameter)或 查詢參數(Query Parameter)來取得呼叫方傳遞的資料。
2. 基本路由範例
下面示範最簡單的 GET 與 POST 路由,說明如何回傳 JSON 與接受請求體(request body)。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# ----------------- GET 範例 -----------------
@app.get("/hello")
async def read_hello():
"""
只回傳一段文字,示範最基本的 GET 路由。
"""
return {"message": "Hello, FastAPI!"}
# ------------------------------------------------
# ----------------- POST 範例 -----------------
class Item(BaseModel):
name: str
price: float
description: str | None = None
@app.post("/items")
async def create_item(item: Item):
"""
從請求體取得 JSON,並回傳建立結果。
FastAPI 會自動根據 Item 類別產生驗證與 OpenAPI 文件。
"""
# 假設此處寫入資料庫...
return {"status": "created", "item": item}
# ------------------------------------------------
說明
@app.get("/hello")→ 當客戶端以 GET 方法請求/hello時,read_hello會被呼叫。@app.post("/items")→ 客戶端以 POST 方法送出 JSON(符合Item結構)到/items,FastAPI 會自動把 JSON 轉成Item物件。
3. 使用路徑參數(Path Parameter)
路徑參數允許我們在 URL 中直接嵌入變數,例如 /users/{user_id}。FastAPI 會自動把字串轉成指定的型別,若轉換失敗則回傳 422 Unprocessable Entity。
@app.get("/users/{user_id}")
async def get_user(user_id: int):
"""
取得單一使用者資訊。
- `user_id` 會被 FastAPI 轉為 int,若不是數字會自動回 422。
"""
# 假設從資料庫撈資料...
fake_user_db = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
user = fake_user_db.get(user_id)
if user:
return {"user_id": user_id, **user}
return {"error": "User not found"}, 404
小技巧:若想限制參數的範圍,可使用 Path、Query 或 Header 的額外參數,例如:
from fastapi import Path
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., gt=0, description="大於 0 的商品編號")
):
return {"item_id": item_id}
4. 多方法共用同一路徑
有時候同一個資源需要支援多種 HTTP 方法(例如 GET、PUT、DELETE),可以分別寫多個裝飾器,或使用 @app.api_route 一次定義多個方法。
# 分別寫法
@app.get("/products/{pid}")
async def get_product(pid: int):
return {"pid": pid, "action": "read"}
@app.put("/products/{pid}")
async def update_product(pid: int, data: dict):
return {"pid": pid, "action": "updated", "data": data}
# 使用 api_route
@app.api_route("/products/{pid}", methods=["GET", "PUT", "DELETE"])
async def product_operations(pid: int, request: Request):
if request.method == "GET":
return {"pid": pid, "action": "read"}
elif request.method == "PUT":
body = await request.json()
return {"pid": pid, "action": "updated", "data": body}
else: # DELETE
return {"pid": pid, "action": "deleted"}
5. 路由前綴(Router)與模組化
在大型專案中,將路由切分成多個 APIRouter 可以提升可讀性與維護性。下面示範如何在子模組 users.py 中建立 router,然後在主程式中掛載。
# users.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/{uid}")
async def get_user(uid: int):
return {"uid": uid, "name": f"User{uid}"}
@router.post("/")
async def create_user(name: str):
return {"uid": 123, "name": name}
# main.py
from fastapi import FastAPI
from users import router as users_router
app = FastAPI()
app.include_router(users_router)
好處
- 前綴(
prefix)自動加在每個路由前,避免重複寫路徑。 - 標籤(
tags)會出現在自動產生的 Swagger 文件,方便文件化。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 路徑參數與查詢參數混用時順序錯誤 | FastAPI 只會把 {} 內的視為路徑參數,? 後的才是查詢參數。 |
確認 URL 結構,必要時使用 Query 明確宣告。 |
忘記加 await |
非同步函式若未加 await,會回傳 coroutine,導致回應不正確。 |
所有 I/O(DB、外部 API)皆使用 await。 |
過度使用 any |
失去型別提示,Swagger 文件會變成 any,降低可讀性。 |
使用 Pydantic 模型或具體型別(int、str、list[Item])。 |
| 路由重複 | 同一路徑、相同方法多次定義會產生 500 Internal Server Error。 | 透過 IDE 或測試確保唯一性,或使用 APIRouter 分區。 |
| 未處理例外 | 未捕捉的例外會直接返回 500,資訊過於模糊。 | 使用 HTTPException、自訂例外處理器(@app.exception_handler)。 |
最佳實踐
- 統一命名規則:路徑使用小寫、底線或中線 (
/users/{user_id})。 - 使用 Pydantic Model:所有請求體與回應皆建議用模型定義,讓驗證與文件同步。
- 分層設計:路由只負責 HTTP 介面,商業邏輯放在 service 層或依賴注入(DI)中。
- 加入版本號:如
/v1/users,方便未來升級 API。 - 測試路由:使用
TestClient撰寫單元測試,確保每條路由的行為符合預期。
實際應用場景
| 場景 | 需求 | 路由設計示例 |
|---|---|---|
| 部落格系統 | 文章 CRUD、列表分頁、搜尋 | /posts(GET 列表)/posts/{post_id}(GET、PUT、DELETE)/posts/search(GET + query) |
| 電商平台 | 商品瀏覽、下單、付款 | /products(GET)/products/{pid}(GET)/orders(POST)/orders/{oid}(GET, PATCH) |
| 即時聊天 | 取得頻道、發送訊息、取得歷史 | /rooms(GET)/rooms/{rid}/messages(GET, POST) |
| IoT 裝置管理 | 註冊裝置、上傳感測資料、查詢狀態 | /devices(POST)/devices/{did}(GET, DELETE)/devices/{did}/data(POST) |
案例說明:假設我們在電商平台實作 商品搜尋,可以結合 查詢參數 與 路徑參數:
@app.get("/products/search")
async def search_products(
q: str = Query(..., min_length=3, description="關鍵字"),
page: int = Query(1, ge=1),
size: int = Query(20, le=100)
):
"""
依關鍵字搜尋商品,支援分頁。
- `q` 必填且長度至少 3。
- `page`、`size` 為分頁資訊。
"""
# 模擬搜尋結果
results = [{"id": i, "name": f"{q} 商品 {i}"} for i in range((page-1)*size, page*size)]
return {"page": page, "size": size, "total": 1234, "items": results}
此路由會自動在 Swagger 中呈現搜尋參數,前端開發者只要依照文件呼叫即可。
總結
- 路由是 FastAPI 的入口,透過
@app.get、@app.post等裝飾器把 HTTP 方法與 URL 直接映射到 Python 函式。 - 路徑參數、查詢參數與 Pydantic Model 讓資料驗證與文件生成變得輕鬆。
APIRouter為大型專案提供模組化、前綴與標籤的管理方式。- 常見的坑包括路由重複、型別未定義、忘記
await,只要遵循 最佳實踐(統一命名、模型化、分層設計、版本化),就能寫出可讀、可維護且安全的 API。
掌握了路由的基本定義與進階寫法後,你就可以開始構建各式各樣的 Web 服務,從簡單的 CRUD 到複雜的微服務架構,都能以 FastAPI 的高效能與自動化文件特性快速交付。祝你開發順利,期待看到你用 FastAPI 打造的精彩作品! 🚀