← 返回大綱
第十六章

API 開發實戰

Building RESTful APIs with Python

概念

什麼是 API?

API(Application Programming Interface)是程式之間溝通的介面

生活比喻

API 就像餐廳的服務生
你(前端)點餐 → 服務生(API)傳達 → 廚房(後端)做菜 → 服務生送餐回來

第十三章 vs 本章

  • 第十三章:呼叫別人的 API
    (我們是顧客)
  • 本章:自己寫 API
    (我們是餐廳老闆)
概念

REST API 設計原則

REST 是最常見的 API 設計風格,用 HTTP 方法 + URL 表達操作:

操作HTTP 方法URL 範例說明
查詢全部GET/api/students取得所有學生
查詢單筆GET/api/students/1取得 ID=1 的學生
新增POST/api/students新增一位學生
更新PUT/api/students/1更新 ID=1 的學生
刪除DELETE/api/students/1刪除 ID=1 的學生
CRUD

Create(新增)、Read(讀取)、Update(更新)、Delete(刪除)是 API 最基本的四種操作。

Flask

Flask — 輕量級 Web 框架

Flask 是 Python 最經典的 Web 框架,語法簡單、適合入門:

pip install flask
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, World!"

if __name__ == "__main__":
    app.run(debug=True)
* Running on http://127.0.0.1:5000
啟動方式

儲存為 app.py,執行 python app.py,然後用瀏覽器打開 http://127.0.0.1:5000 就能看到結果。

Flask

Flask 路由與 HTTP 方法

@app.route() 裝飾器定義不同的 URL 路徑和 HTTP 方法:

from flask import Flask, request, jsonify

app = Flask(__name__)

# GET — 首頁
@app.route("/")
def index():
    return "歡迎來到我的 API!"

# GET — 帶路徑參數
@app.route("/hello/<name>")
def hello(name):
    return f"你好,{name}!"

# POST — 接收 JSON 資料
@app.route("/echo", methods=["POST"])
def echo():
    data = request.get_json()
    return jsonify({"received": data})
重要函式

request.get_json() 取得請求的 JSON 資料,jsonify() 回傳 JSON 格式回應。

Flask 實作

Flask CRUD API 實作(1/3)

建立一個學生管理 API,先定義資料結構和查詢功能:

from flask import Flask, request, jsonify

app = Flask(__name__)

# 用 list 模擬資料庫
students = [
    {"id": 1, "name": "Ian", "score": 90},
    {"id": 2, "name": "Amy", "score": 85},
]
next_id = 3

# GET /api/students — 取得所有學生
@app.route("/api/students")
def get_students():
    return jsonify(students)

# GET /api/students/<id> — 取得單一學生
@app.route("/api/students/<int:student_id>")
def get_student(student_id):
    student = next((s for s in students if s["id"] == student_id), None)
    if student is None:
        return jsonify({"error": "找不到學生"}), 404
    return jsonify(student)

↓ 往下看新增與更新

Flask 實作

Flask CRUD API 實作(2/3)

新增(POST)與更新(PUT):

# POST /api/students — 新增學生
@app.route("/api/students", methods=["POST"])
def create_student():
    global next_id
    data = request.get_json()

    # 驗證必要欄位
    if not data or "name" not in data:
        return jsonify({"error": "缺少 name 欄位"}), 400

    student = {
        "id": next_id,
        "name": data["name"],
        "score": data.get("score", 0)    # 預設 0 分
    }
    students.append(student)
    next_id += 1
    return jsonify(student), 201             # 201 = Created

# PUT /api/students/<id> — 更新學生
@app.route("/api/students/<int:student_id>", methods=["PUT"])
def update_student(student_id):
    student = next((s for s in students if s["id"] == student_id), None)
    if student is None:
        return jsonify({"error": "找不到學生"}), 404

    data = request.get_json()
    student["name"] = data.get("name", student["name"])
    student["score"] = data.get("score", student["score"])
    return jsonify(student)

↓ 往下看刪除與啟動

Flask 實作

Flask CRUD API 實作(3/3)

刪除(DELETE)與啟動伺服器:

# DELETE /api/students/<id> — 刪除學生
@app.route("/api/students/<int:student_id>", methods=["DELETE"])
def delete_student(student_id):
    global students
    student = next((s for s in students if s["id"] == student_id), None)
    if student is None:
        return jsonify({"error": "找不到學生"}), 404

    students = [s for s in students if s["id"] != student_id]
    return jsonify({"message": f"已刪除學生 {student['name']}"})

# 啟動伺服器
if __name__ == "__main__":
    app.run(debug=True, port=5000)
注意

這裡用 list 模擬資料庫,伺服器重啟後資料會消失。實際專案要搭配第十二章學的資料庫。

測試

用 curl 測試你的 API

在終端機中使用 curl 指令測試各個 API:

# 查詢所有學生
curl http://127.0.0.1:5000/api/students

# 查詢單一學生
curl http://127.0.0.1:5000/api/students/1

# 新增學生
curl -X POST http://127.0.0.1:5000/api/students \
     -H "Content-Type: application/json" \
     -d '{"name": "Tom", "score": 88}'

# 更新學生
curl -X PUT http://127.0.0.1:5000/api/students/1 \
     -H "Content-Type: application/json" \
     -d '{"score": 95}'

# 刪除學生
curl -X DELETE http://127.0.0.1:5000/api/students/1
其他測試工具

也可以用 Postman 或 VS Code 的 REST Client 擴充套件,提供圖形介面更方便。

FastAPI

FastAPI — 現代高效能框架

FastAPI 是 Python 目前最熱門的 API 框架,速度快、自動產生文件:

pip install fastapi uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, World!"}

@app.get("/hello/{name}")
def hello(name: str):
    return {"message": f"你好,{name}!"}

啟動方式:

uvicorn app:app --reload
自動 API 文件

啟動後打開 http://127.0.0.1:8000/docs 就能看到自動產生的互動式 API 文件(Swagger UI),可以直接在網頁上測試!

FastAPI 實作

FastAPI CRUD 實作(1/2)

同樣的學生管理 API,用 FastAPI 寫法更簡潔:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

# 定義資料模型(自動驗證欄位)
class StudentCreate(BaseModel):
    name: str
    score: int = 0

class Student(BaseModel):
    id: int
    name: str
    score: int

students = [
    {"id": 1, "name": "Ian", "score": 90},
    {"id": 2, "name": "Amy", "score": 85},
]
next_id = 3

@app.get("/api/students", response_model=list[Student])
def get_students():
    return students

@app.get("/api/students/{student_id}", response_model=Student)
def get_student(student_id: int):
    student = next((s for s in students if s["id"] == student_id), None)
    if not student:
        raise HTTPException(status_code=404, detail="找不到學生")
    return student

↓ 往下看新增、更新、刪除

FastAPI 實作

FastAPI CRUD 實作(2/2)

@app.post("/api/students", response_model=Student, status_code=201)
def create_student(data: StudentCreate):
    global next_id
    student = {"id": next_id, "name": data.name, "score": data.score}
    students.append(student)
    next_id += 1
    return student

@app.put("/api/students/{student_id}", response_model=Student)
def update_student(student_id: int, data: StudentCreate):
    student = next((s for s in students if s["id"] == student_id), None)
    if not student:
        raise HTTPException(status_code=404, detail="找不到學生")
    student["name"] = data.name
    student["score"] = data.score
    return student

@app.delete("/api/students/{student_id}")
def delete_student(student_id: int):
    global students
    student = next((s for s in students if s["id"] == student_id), None)
    if not student:
        raise HTTPException(status_code=404, detail="找不到學生")
    students = [s for s in students if s["id"] != student_id]
    return {"message": f"已刪除學生 {student['name']}"}
Pydantic 自動驗證

FastAPI 用 BaseModel 定義資料模型,如果前端傳的欄位格式不對,會自動回傳 422 錯誤,不需要手動驗證。

比較

Flask vs FastAPI 怎麼選?

FlaskFastAPI
難度簡單易懂需懂 type hints
效能普通(同步)極快(支援非同步)
資料驗證需手動或裝擴充Pydantic 自動驗證
API 文件需額外設定自動產生 Swagger UI
生態系歷史悠久、套件最多快速成長中
適合場景小型專案、學習用正式 API、生產環境
建議

初學者先用 Flask 理解 API 基本概念,之後進階到 FastAPI 開發正式專案。

最佳實踐

API 設計常見規範

URL 命名

  • 名詞,不用動詞
    /api/students (O)
    /api/getStudents (X)
  • 複數形式
    /api/students (O)
    /api/student (X)
  • 小寫連字號
    /api/course-records

回應格式

  • 統一用 JSON 格式
  • 回傳適當的狀態碼
    200、201、400、404、500
  • 錯誤時回傳錯誤訊息
# 錯誤回應範例
{
  "error": "找不到學生",
  "status": 404
}
進階

API 專案資料夾結構

當 API 變複雜時,建議用以下結構組織程式碼:

my_api/
├── app.py              # 主程式入口
├── requirements.txt    # 套件清單
├── models/             # 資料模型
│   └── student.py
├── routes/             # 路由(各功能的 API)
│   ├── __init__.py
│   └── students.py
├── services/           # 商業邏輯
│   └── student_service.py
└── tests/              # 測試
    └── test_students.py
為什麼要拆分?

把路由、模型、邏輯分開,每個檔案各司其職,程式碼更好維護和測試。

實作練習

動手試試看

建立一個待辦事項(Todo)API,支援以下功能:

方法路徑功能
GET/api/todos取得所有待辦事項
POST/api/todos新增待辦事項
PUT/api/todos/<id>標記為完成 / 更新內容
DELETE/api/todos/<id>刪除待辦事項

每個 Todo 包含:

{"id": 1, "title": "寫 Python 作業", "done": false}
提示

參考前面的學生管理 API,把 students 改成 todos,欄位改成 titledone 即可!

第十六章完成!

學會了用 Flask 和 FastAPI 建立 RESTful API。