WeHelp
FastAPI 是簡單易學、現代化、高效能的後端系統開發框架,近代 Python 最受歡迎的技術堆疊之一。
  1. 簡介、安裝、快速開始
  2. 主機名稱、埠號
  3. 路徑、路由處理
  4. 路徑參數處理
  5. 查詢參數處理
  6. 靜態檔案處理
  7. 回應格式處理
  8. 網址導向
  9. 網頁表單互動
  10. 網頁表單 POST 方法
  11. 樣板引擎
  12. 請求文本處理
  13. 常見的連線方法
  14. 參數資料驗證
  15. 使用者狀態管理
  16. 資料庫連線
常見的連線方法
以下以「待辦事項 Todo」為例,說明 HTTP 方法與 FastAPI 的處理方式。 ## 一、GET、POST、PUT、PATCH、DELETE 的概念 | 方法 | 用途 | 特性 | |---|---|---| | `GET` | 取得資料 | 不應修改伺服器資料 | | `POST` | 新增資料或執行動作 | 通常不是冪等操作,每次呼叫可能新增一筆 | | `PUT` | 完整更新資料 | 通常代表以新資料取代原有資源,具冪等性 | | `PATCH` | 部分更新資料 | 只修改指定欄位 | | `DELETE` | 刪除資料 | 刪除指定資源,通常具冪等性 | ### 範例 假設資源網址為: ```text /api/todos/1 ``` #### GET ```http GET /api/todos/1 ``` 取得編號為 `1` 的待辦事項。 #### POST ```http POST /api/todos Content-Type: application/json { "title": "學習 FastAPI", "completed": false } ``` 新增一筆待辦事項,伺服器通常會產生新的 `id`。 #### PUT ```http PUT /api/todos/1 Content-Type: application/json { "title": "完整修改標題", "completed": true } ``` 以完整內容取代編號 `1` 的資料。 #### PATCH ```http PATCH /api/todos/1 Content-Type: application/json { "completed": true } ``` 只修改 `completed` 欄位,其他欄位保持不變。 #### DELETE ```http DELETE /api/todos/1 ``` 刪除編號為 `1` 的待辦事項。 --- # 二、FastAPI 如何處理不同 HTTP 方法 FastAPI 使用「路由裝飾器」將 URL 與 HTTP 方法對應到 Python 函式。 ```python from fastapi import FastAPI app = FastAPI() @app.get("/todos") def get_todos(): return {"message": "取得所有待辦事項"} @app.post("/todos") def create_todo(): return {"message": "新增待辦事項"} @app.put("/todos/{todo_id}") def replace_todo(todo_id: int): return {"message": f"完整更新 Todo {todo_id}"} @app.patch("/todos/{todo_id}") def update_todo(todo_id: int): return {"message": f"部分更新 Todo {todo_id}"} @app.delete("/todos/{todo_id}") def delete_todo(todo_id: int): return {"message": f"刪除 Todo {todo_id}"} ``` 其中: ```python @app.get("/todos") ``` 表示當使用者以 `GET /todos` 存取時,執行下面的函式。 ```python @app.patch("/todos/{todo_id}") ``` 其中 `{todo_id}` 是路徑參數,例如: ```text PATCH /todos/3 ``` FastAPI 會自動將 `3` 傳入函式的 `todo_id` 參數。 --- # 三、完整 FastAPI 範例 ## 1. 安裝套件 ```bash pip install fastapi uvicorn ``` ## 2. 建立 `main.py` ```python from typing import Optional from fastapi import FastAPI, HTTPException, Response, status from pydantic import BaseModel app = FastAPI(title="Todo API") # 完整新增或更新時使用 class TodoCreate(BaseModel): title: str completed: bool = False # PATCH 使用,所有欄位都是可選的 class TodoUpdate(BaseModel): title: Optional[str] = None completed: Optional[bool] = None # 模擬資料庫 todos = { 1: { "id": 1, "title": "學習 FastAPI", "completed": False }, 2: { "id": 2, "title": "學習 fetch", "completed": False } } next_id = 3 # GET:取得所有待辦事項 @app.get("/api/todos") def get_todos(): return list(todos.values()) # GET:取得單一待辦事項 @app.get("/api/todos/{todo_id}") def get_todo(todo_id: int): todo = todos.get(todo_id) if todo is None: raise HTTPException( status_code=404, detail="找不到此待辦事項" ) return todo # POST:新增待辦事項 @app.post("/api/todos", status_code=201) def create_todo(todo_data: TodoCreate): global next_id new_todo = { "id": next_id, "title": todo_data.title, "completed": todo_data.completed } todos[next_id] = new_todo next_id += 1 return new_todo # PUT:完整取代一筆待辦事項 @app.put("/api/todos/{todo_id}") def replace_todo(todo_id: int, todo_data: TodoCreate): if todo_id not in todos: raise HTTPException( status_code=404, detail="找不到此待辦事項" ) todos[todo_id] = { "id": todo_id, "title": todo_data.title, "completed": todo_data.completed } return todos[todo_id] # PATCH:部分更新一筆待辦事項 @app.patch("/api/todos/{todo_id}") def update_todo(todo_id: int, todo_data: TodoUpdate): if todo_id not in todos: raise HTTPException( status_code=404, detail="找不到此待辦事項" ) # exclude_unset=True: # 只取得請求中實際送出的欄位 update_data = todo_data.model_dump(exclude_unset=True) todos[todo_id].update(update_data) return todos[todo_id] # DELETE:刪除待辦事項 @app.delete("/api/todos/{todo_id}", status_code=204) def delete_todo(todo_id: int): if todo_id not in todos: raise HTTPException( status_code=404, detail="找不到此待辦事項" ) del todos[todo_id] # 204 No Content 不應該回傳 JSON 內容 return Response(status_code=status.HTTP_204_NO_CONTENT) ``` 啟動伺服器: ```bash uvicorn main:app --reload ``` 啟動後可以開啟: ```text http://127.0.0.1:8000/docs ``` FastAPI 會自動產生 Swagger API 測試介面。 --- # 四、前端使用 `fetch()` 呼叫 API 建立 `index.html`: ```html <!DOCTYPE html> <html lang="zh-Hant"> <head> <meta charset="UTF-8"> <title>Todo API 範例</title> </head> <body> <h1>Todo 清單</h1> <input id="titleInput" type="text" placeholder="輸入待辦事項"> <button onclick="createTodo()">新增</button> <button onclick="loadTodos()">重新載入</button> <ul id="todoList"></ul> <script> const API_URL = "http://127.0.0.1:8000/api/todos"; // GET:取得所有資料 async function loadTodos() { const response = await fetch(API_URL); if (!response.ok) { alert("取得資料失敗"); return; } const todos = await response.json(); renderTodos(todos); } // 將資料顯示在畫面上 function renderTodos(todos) { const list = document.querySelector("#todoList"); list.innerHTML = ""; todos.forEach(todo => { const li = document.createElement("li"); li.innerHTML = ` <span> ${todo.id}. ${todo.title} ${todo.completed ? "✅" : "⬜"} </span> <button onclick="toggleTodo(${todo.id}, ${todo.completed})"> PATCH 完成狀態 </button> <button onclick="replaceTodo(${todo.id})"> PUT 完整更新 </button> <button onclick="deleteTodo(${todo.id})"> DELETE </button> `; list.appendChild(li); }); } // POST:新增資料 async function createTodo() { const input = document.querySelector("#titleInput"); const title = input.value.trim(); if (!title) { alert("請輸入待辦事項"); return; } const response = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: title, completed: false }) }); if (!response.ok) { alert("新增失敗"); return; } input.value = ""; await loadTodos(); } // PATCH:只修改 completed 欄位 async function toggleTodo(todoId, currentCompleted) { const response = await fetch(`${API_URL}/${todoId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ completed: !currentCompleted }) }); if (!response.ok) { alert("部分更新失敗"); return; } await loadTodos(); } // PUT:完整取代資料 async function replaceTodo(todoId) { const newTitle = prompt("請輸入新的標題:"); if (!newTitle) { return; } const response = await fetch(`${API_URL}/${todoId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: newTitle, completed: false }) }); if (!response.ok) { alert("完整更新失敗"); return; } await loadTodos(); } // DELETE:刪除資料 async function deleteTodo(todoId) { const confirmed = confirm("確定要刪除嗎?"); if (!confirmed) { return; } const response = await fetch(`${API_URL}/${todoId}`, { method: "DELETE" }); if (!response.ok) { alert("刪除失敗"); return; } await loadTodos(); } // 頁面載入時先取得資料 loadTodos(); </script> </body> </html> ``` --- # 五、`fetch()` 的基本格式 ## GET ```javascript const response = await fetch("/api/todos"); const data = await response.json(); ``` ## POST ```javascript const response = await fetch("/api/todos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: "新的待辦事項", completed: false }) }); ``` ## PUT ```javascript await fetch("/api/todos/1", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: "完整更新後的標題", completed: true }) }); ``` ## PATCH ```javascript await fetch("/api/todos/1", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ completed: true }) }); ``` ## DELETE ```javascript await fetch("/api/todos/1", { method: "DELETE" }); ``` --- # 六、跨來源請求與 CORS 如果前端和 FastAPI 使用不同來源,例如: ```text 前端:http://127.0.0.1:5500 後端:http://127.0.0.1:8000 ``` 瀏覽器可能會阻擋請求,此時 FastAPI 需要設定 CORS。 在 `main.py` 加入: ```python from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=[ "http://127.0.0.1:5500" ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ``` 開發階段也可以暫時允許所有來源: ```python app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) ``` 正式環境則應限制 `allow_origins`,不要直接允許所有來源。 簡單來說: - `GET`:讀取資料 - `POST`:新增資料 - `PUT`:完整取代資料 - `PATCH`:部分修改資料 - `DELETE`:刪除資料 - FastAPI 使用 `@app.get()`、`@app.post()` 等裝飾器處理不同方法 - 前端則透過 `fetch()` 指定 `method`、`headers` 與 `body` 來呼叫 API
相關學習地圖、教學課程
Python 後端工程、資料庫
從 0 開始,成為後端工程師的學習路徑。