Web Day 1 系列導覽:從腳本到系統的學習地圖
執行需求:CPU 可跑。今天是「Web 系統實戰:用 FastAPI 打造能上線的後端」系列的第一篇,我們不寫複雜的服務,先把整個四十五篇的全貌攤開來。讀完之後,你會知道這系列為什麼從「腳本」走到「系統」、每一天會碰到什麼主題、版本怎麼對齊,也會看到一個能在本機直接跑起來的最小 FastAPI 範例,作為整趟旅程的起點。
引言
很多寫 Python 的人(包括我自己剛入行的時候)都停留在「寫腳本」的階段:拿 Jupyter Notebook 或一支 .py 檔把資料清洗完、把模型訓練完、把報表輸出完,整件事就算結束了。這種方式在「自己用」或「單次實驗」的場景沒什麼不好,可是一旦要把成果交給別人用、要讓別人透過瀏覽器或手機 App 來存取、要能在伺服器上 24 小時提供服務,「腳本」就不夠用了。我們需要一個「系統」:有清楚的輸入與輸出、有穩定的錯誤處理、有資料庫、有認證、有部署流程。
這系列就是為了這個轉變設計的。我們假設你已經會 Python 與基礎的命令列操作(前三個系列「Python 從入門到 PyTorch 深度學習」、「PyTorch 電腦視覺進階」、「NLP/LLM 應用」的讀者應該都已具備),接下來要帶你從「會寫腳本」走到「能獨立交付一個能上線的後端系統」。整個系列以 FastAPI 為核心框架,搭配 SQLite、PostgreSQL、Redis、HTMX、Next.js、Docker 等開源工具,最後用一個貫穿專案「預約管理系統」把所有東西串起來。
這篇文章會做五件事:第一,解釋「腳本」與「系統」的本質差別,以及這個系列想幫你跨越的距離;第二,畫出四十五天的學習地圖;第三,列出整個系列會用到的版本基準;第四,給出你的第一個 FastAPI 服務(今天就能跑起來);第五,回答「沒有 Docker、沒有雲端帳號也能跟完嗎?」這個最常被問到的問題。
從腳本到系統:跨越的距離
腳本(script)通常是一支可以單獨執行的 Python 程式,輸入是檔案、輸出也是檔案,整個生命週期從開始到結束往往只有幾秒到幾分鐘。系統(system)則不一樣:它是個長期運行的程式,會不斷接收來自四面八方的請求、把資料寫進資料庫、要在使用者出錯時優雅回應、要在硬碟滿了或網路斷掉時存活下來。兩者寫程式的心態完全不同。
寫腳本時,我們關心的是「能不能跑出我要的結果」;寫系統時,我們關心的是「能不能在各種奇怪的輸入、奇怪的時機、奇怪的網路狀況下,都交出一致的回應」。這中間的差距,靠的不是單一技巧,而是整套工程紀律:版本管理、測試、錯誤處理、資料持久化、認證、部署、維運。本系列就是要把這些紀律一個一個介紹給你,並讓你有機會實際動手做。
這系列不假設你有任何網頁前端經驗,但我們會在最後幾篇用最少的前端(HTMX 或 Next.js)展示「怎麼把 API 接到介面上」,讓你看到完整的端到端流程。即使你的工作只專注在後端,理解前後端的銜接點也能讓你在跟前端同事溝通時更順暢。我們也不假設你已經會 Docker,但前段基礎篇都不需要 Docker,到上線章節才會逐步引入。
這系列刻意把所有範例都設計成「可獨立跑」的最小版本。每一支程式都可以直接複製貼上執行,預設使用 SQLite(Python 內建的輕量資料庫)做儲存,不需要任何外部服務;只有少數幾篇會用到 Docker(Day 30、33、41),那幾篇會清楚標示,讓沒有 Docker 的讀者可以選擇跳過或用本機方式替代。這種設計的目標是「讓你從家裡的筆電就能開始學」,而不是「你得先準備好雲端帳號」。
四十五天的學習地圖
整個系列分成七個主題區塊,每個區塊約五到九天,按「由內而外、由小到大」的順序排列。下表把整體安排攤開來,讓你有心理準備,知道接下來每一天會走到哪裡。
| 區塊 | 天數 | 核心主題 | 代表工具 |
|---|---|---|---|
| 導論 | Web Day 1–2 | 系列地圖、環境、工具鏈、專案結構 | uv、VS Code、Git、FastAPI |
| 基礎 | Web Day 3–10 | HTTP、REST、FastAPI 路由、驗證、SQLModel、CRUD、關聯、遷移、錯誤處理 | FastAPI、Pydantic v2、SQLModel、SQLite、Alembic |
| 安全 | Web Day 11–15 | 密碼雜湊、JWT、OAuth2、檔案上傳、輸入防護 | passlib、PyJWT、Authlib |
| 品質 | Web Day 16–22 | pytest、非同步、背景任務、快取、排程、日誌 | pytest、httpx、Celery、Redis、APScheduler |
| 前端整合 | Web Day 23–28 | HTMX、Next.js 串接、認證整合、WebSocket | HTMX、Next.js、Jinja2 |
| 上線 | Web Day 29–34 | 設定管理、Docker、PostgreSQL、CI/CD、反向代理、監控 | Docker、Caddy、GitHub Actions、Prometheus |
| 貫穿專案 | Web Day 35–45 | 預約管理系統的資料模型、認證、預約流程、通知、後台、驗收、上線、監控、交接 | 整體專案整合 |
這張表的重點不是「哪天要做什麼」,而是「為什麼這個順序」。我們刻意把 HTTP 與 FastAPI 放在最前面,因為這些是後端系統的基石;接著才是資料庫與 CRUD;然後才是認證與安全。測試、背景任務、快取這類「品質」主題則放在能跑出東西之後,因為有東西可以測、有東西可以丟到背景,才學得到效果。最後才走 Docker、CI/CD、監控這些上線議題,因為前面的基礎不穩就上線,會出大問題。
貫穿專案「預約管理系統」會從 Web Day 35 開始,用十一天的篇幅把前面學到的東西全部串起來:攝影工作室或健身教練的線上預約系統,支援管理者與客戶兩種角色、有衝突檢查、有通知、HTMX 後台、Next.js 前台、Docker Compose 一鍵上線。這個專案的所有資料都是虛構的,目的是讓你在沒有真實客戶資料的情況下,也能完整練習整套流程。
版本與環境基準
這系列固定在 2025 年 7 月的主流版本上,所有範例都會在這些版本下測過。固定版本的目的不是要你永遠停在這個版本,而是要你有一段時間可以安心學,避免「套件升級→API 改了→範例壞了」這種追著版本跑的窘境。
| 工具 | 版本 | 用途 |
|---|---|---|
| Python | 3.13 | 直譯器 |
| FastAPI | 0.116 | Web 框架 |
| Pydantic | 2.11 | 資料驗證 |
| SQLModel | 0.0.24 | ORM 與資料模型 |
| SQLAlchemy | 2.0.41 | 底層 ORM |
| uvicorn | 0.35 | ASGI 伺服器 |
| httpx | 0.28 | HTTP 測試呼叫端 |
| uv | 0.7 | 套件與環境管理 |
| pytest | 8.4 | 測試框架 |
| Alembic | 1.16 | 資料庫遷移 |
| HTMX | 2.0 | 輕量前端互動 |
| Next.js | 15 | 前端框架(最後幾篇用) |
這份清單會隨著系列推進而擴充,例如 Web Day 19 會加入 Celery 5.4、Web Day 20 會加入 Redis 8、Web Day 30 會說明 Docker 的版本對應。本系列刻意只使用 2025 年 7 月之前已存在且具知名度的套件,避免你學到一半發現「這東西已經改名了」或「這個 API 在新版被拔掉了」。
整個系列都是 CPU 可跑的——不需要 GPU,也不需要特殊硬體。少數幾篇(Web Day 30、33、41)會用 Docker,因為那幾篇的目標就是「把服務打包成容器」。如果你本機沒有安裝 Docker,沒關係,那幾篇會標示清楚,你可以選擇跳過或先用本機的方式做替代。
完整實作:你的第一個 FastAPI 服務
介紹完地圖與版本,今天最後要帶你做一件事:在本機把一個最小的 FastAPI 服務跑起來。這個範例會在後續四十四篇反覆延伸,今天先求「能跑、能被另一支程式呼叫」。我們用 uv 建立環境(Web Day 2 會詳細介紹,今天先用最快的方式)。
如果你本機還沒裝 uv,可以先用 pip 跳過這一步;但為了跟整個系列一致,我們強烈建議從 Day 2 之後改用 uv。執行需求標示寫在第一段,這個範例只需要 Python 3.13 與 FastAPI 0.116,CPU 就能跑。
# 安裝 FastAPI 與 uvicorn
# 需先準備 Python 3.13 的環境(Day 2 會講細節)
# 在命令列執行:
# pip install "fastapi==0.116" "uvicorn[standard]==0.35"
import fastapi
import uvicorn
print(f"FastAPI 版本:{fastapi.__version__}")
# 輸出:FastAPI 版本:0.116.x(實際版本會依套件釋出略有不同)
print(f"uvicorn 版本:{uvicorn.__version__}")
# 輸出:uvicorn 版本:0.35.x
這個小段只是確認版本對齊,安裝完成後就可以寫第一支 API。建立檔案 main.py:
# main.py
# 第一個 FastAPI 服務:回傳 hello 與目前時間
from datetime import datetime, timezone
from fastapi import FastAPI
app = FastAPI(title="第一個 FastAPI 服務", version="0.1.0")
@app.get("/")
def read_root():
# 最簡單的端點:回傳一個字典
return {"message": "Hello, FastAPI!", "service": "web-day-01"}
@app.get("/health")
def health_check():
# 健康檢查端點:給監控系統或負載平衡器用
return {"status": "ok"}
@app.get("/now")
def now():
# 帶時間戳記的端點
current = datetime.now(timezone.utc).isoformat()
return {"now_utc": current}
# 啟動指令(在命令列):
# uvicorn main:app --reload --port 8000
# 之後用瀏覽器打開 http://127.0.0.1:8000/
# 自動說明頁面:http://127.0.0.1:8000/docs
這支程式定義了三個端點(endpoint):/ 回傳簡單的 JSON、/health 是健康檢查、/now 回傳 UTC 時間。裝飾器 @app.get 表示「用 HTTP GET 方法存取這個路徑時,呼叫下面的函式」。FastAPI 會自動把函式的回傳值(字典)轉成 JSON 格式,這是 Web Day 4 會深入的內容,今天先看到結果。
啟動伺服器(這是命令列指令,不是 Python):
# 在 main.py 所在目錄執行
uvicorn main:app --reload --port 8000
# 啟動後會看到類似這樣的輸出:
# INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
# INFO: Started reloader process [28704]
# INFO: Started server process [28709]
# INFO: Waiting for application startup.
# INFO: Application startup complete.
--reload 參數讓伺服器在你修改程式碼後自動重新啟動,開發階段非常好用;上線之後一定要拿掉,改用正式的管理方式(Day 30 會講)。啟動成功後,http://127.0.0.1:8000/ 應該會回傳一段 JSON;http://127.0.0.1:8000/docs 會打開自動產生的互動式 API 說明頁面(Swagger UI)。
伺服器跑起來之後,用一支 httpx 程式測試剛剛的三個端點:
# test_smoke.py
# 用 httpx 打三個端點,確認服務正常
import httpx
BASE = "http://127.0.0.1:8000"
with httpx.Client(base_url=BASE, timeout=5.0) as client:
r1 = client.get("/")
print(r1.status_code, r1.json())
# 輸出(範例):200 {'message': 'Hello, FastAPI!', 'service': 'web-day-01'}
r2 = client.get("/health")
print(r2.status_code, r2.json())
# 輸出(範例):200 {'status': 'ok'}
r3 = client.get("/now")
print(r3.status_code, r3.json())
# 輸出(範例):200 {'now_utc': '2025-07-20T08:30:00+00:00'}
httpx 是這個系列會一直用到的 HTTP 呼叫端,比 requests 多了原生非同步支援(Day 18 會示範)。在這個範例裡,我們用 with 把 client 包起來,確保連線正確關閉;三個端點都應該回傳 200 狀態碼與 JSON 主體。如果其中一個失敗,先確認 uvicorn 還在跑(另一個終端機視窗),再檢查 port 有沒有被其他程式占用。
如果你是第一次接觸 HTTP 自動化測試,這支腳本的幾個設計值得記起來。第一,base_url 用在 Client(...) 裡而不是每次 get 都帶完整 URL,這樣以後要換 base URL 只改一個地方。第二,timeout=5.0 是明確寫死的——預設 httpx 對所有操作有預設逾時,但對「單元測試」場景,明確寫一個較短的值可以讓卡住的測試及早失敗。第三,with 區塊結束時會自動呼叫 close(),確保 TCP 連線不會洩漏。這三個寫法從 Day 1 就養起來,後面就不必再改。
最後用 curl 也對照一次。curl 是幾乎所有作業系統都內建的指令,部署到伺服器上除錯時很方便:
# 用 curl 測試
curl -s http://127.0.0.1:8000/
# 輸出:{"message":"Hello, FastAPI!","service":"web-day-01"}
curl -s http://127.0.0.1:8000/health
# 輸出:{"status":"ok"}
# 只顯示狀態碼
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/now
# 輸出:200
這個範例雖然簡單,但它涵蓋了後端系統的所有基本動作:定義端點、啟動伺服器、用程式測試。今天先把這個迴圈跑順,明天我們就把支撐這個迴圈的工具鏈(uv、VS Code、Git、專案結構)一個一個建起來。
常見錯誤與踩雷
第一次跑 FastAPI 的人,最常見的問題是「打 http://127.0.0.1:8000/ 沒回應」。這通常有三個成因:第一,uvicorn 還沒啟動成功,看一下終端機有沒有 Application startup complete 字樣;第二,連到別的 port 了,預設是 8000,但你可能先前有別的服務在跑,uvicorn 會改用 8001;第三,打到 localhost 在某些系統會被 IPv6 解析掉,改用 127.0.0.1 通常就解決。
第二個常見的踩雷是「改完 main.py 但伺服器沒更新」。這通常代表 --reload 沒生效,或是你在不是專案根目錄啟動 uvicorn,導致檔案監看失效。解法是把啟動指令的 cwd 切到 main.py 所在目錄,或者用 uvicorn --app-dir . main:app --reload 明確指定。
第三個是「裝飾器寫錯位置」。@app.get("/") 必須緊貼在函式定義的上方,中間不能有別的程式碼(裝飾器本質上是把下面那行函式包起來再傳給裝飾器工廠,所以中間夾雜其他敘述會破壞結構)。如果你看到「endpoint 沒註冊到」或「函式被當成一般函式而非路由」,第一個檢查的就是裝飾器的位置與縮排。
效能與實務提醒
開發階段用 --reload 是對的,但千萬不要把這個參數帶到正式環境。reload 機制需要額外的子行程與檔案監看,會拖慢啟動速度、浪費記憶體、並且在容器化部署時容易出問題(容器內重新載入檔案會產生競爭狀態)。正式環境請用 uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4,workers 數量依 CPU 核心數調整,Day 30 會有更完整的部署說明。
這個系列的範例刻意都做得很小,是為了讓你能在幾秒內看到結果。但真實世界的 API 往往要在數百毫秒內回應、在數萬 QPS 下不當機。這些議題會在 Web Day 18(非同步)、Web Day 20(快取)、Web Day 43(壓力測試)逐步討論。今天的目標是「讓你能跑起來」,效能調校留給後面。
另外提醒一件事:寫後端最大的成本往往不是程式碼本身,而是「環境沒弄好」。很多人花一整天卡在 uvicorn 啟動不起來、套件版本衝突、port 被占用,結果什麼 API 都沒寫到。Web Day 2 會用整整一篇把環境議題一次處理掉,建議你跟著走完,不要跳過。
進階練習:三種方式確認服務正常
確認 FastAPI 服務是否正常啟動,是日後每天寫程式都會做的事。除了前面的 httpx 與 curl,還有第三種方式:直接走訪 FastAPI app 物件,看它註冊了哪些路由。這在做自動健康檢查與除錯時特別有用。
# introspect_app.py
# 走訪 FastAPI 物件,把所有註冊的路由列出來
from main import app
def list_routes(application):
# 走訪 FastAPI 物件的 routes 屬性
routes = []
for route in application.routes:
if hasattr(route, "path") and hasattr(route, "methods"):
methods = sorted(route.methods - {"HEAD"}) # 去掉 HEAD
routes.append((", ".join(methods), route.path))
return routes
for method, path in list_routes(app):
print(f"{method:8s} {path}")
# 輸出(範例):
# GET /
# GET /health
# GET /now
# GET /docs
# GET /redoc
# GET /openapi.json
這支腳本對「你不知道程式裡到底有哪些端點」的情境特別有用——接手別人的專案、或快速確認自己的端點有沒有寫對路徑時,直接列出來比翻程式碼快很多。routes.methods - {"HEAD"} 是 FastAPI 自動幫每個 GET 加上的 HEAD 方法(用於 HTTP 快取驗證),通常不會單獨呼叫,所以過濾掉。
第二個進階練習:寫一個「服務探測器」主動偵測本地服務是否還活著。這在 CI/CD 與監控系統裡很常見——部署之後,部署腳本會自動打健康檢查端點,確認服務真的有起來再繼續。
# health_probe.py
# 持續偵測本地 FastAPI 健康狀態;按下 Ctrl+C 中止
import time
import httpx
URL = "http://127.0.0.1:8000/health"
INTERVAL_SECONDS = 2.0
MAX_FAILS = 3 # 連續失敗三次就停止並提示
def probe() -> bool:
try:
r = httpx.get(URL, timeout=2.0)
return r.status_code == 200 and r.json().get("status") == "ok"
except Exception:
return False
def main():
fails = 0
while True:
ok = probe()
if ok:
fails = 0
print(f"[OK] {time.strftime('%H:%M:%S')} 健康")
else:
fails += 1
print(f"[FAIL] {time.strftime('%H:%M:%S')} 不健康(連續 {fails} 次)")
if fails >= MAX_FAILS:
print("超過容忍次數,停止探測")
break
time.sleep(INTERVAL_SECONDS)
main()
這支程式碼做的事情很簡單:每兩秒打一次 /health,連續失敗三次就停止。在真實世界裡,這種探測器會跟 PagerDuty、Slack 等通知系統整合,服務掛了自動發訊息給值班工程師。今天的版本只是骨架,明天起會逐漸加入這些維運設計。
第三個練習:用 Python 的標準函式庫 subprocess 啟動 uvicorn,再用同支程式發請求測試——這是「在 CI 環境跑整合測試」常見的設計:
# integration_test.py
# 啟動 uvicorn、測試、關閉,全部都在 Python 裡
import subprocess
import time
import httpx
def start_server() -> subprocess.Popen:
# 啟動 uvicorn 子行程
return subprocess.Popen(
["uvicorn", "main:app", "--port", "8001"], # 用 8001 避免與開發用的 8000 衝突
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def main():
server = start_server()
try:
time.sleep(1.5) # 等伺服器啟動
with httpx.Client(base_url="http://127.0.0.1:8001", timeout=5.0) as client:
r = client.get("/")
assert r.status_code == 200
assert r.json()["message"] == "Hello, FastAPI!"
print("整合測試通過:", r.json())
# 輸出(範例):整合測試通過: {'message': 'Hello, FastAPI!', 'service': 'web-day-01'}
finally:
server.terminate()
server.wait(timeout=5)
main()
這支腳本示範了「啟動 → 測試 → 關閉」一條龍的整合測試寫法。subprocess.Popen 啟動 uvicorn 當子行程,finally 確保測試結束或失敗時子行程都會被關閉(避免測試結束後留下一堆殭屍行程)。--port 8001 是故意的——避免與正在跑開發伺服器的 8000 衝突。本系列 Web Day 16 介紹 pytest 時,會把這套機制整合到 TestClient 裡,到時你會看到 FastAPI 內建的整合測試更簡潔。
小結
今天是系列的第一篇,我們把四十五天的學習地圖畫出來了:你會從 HTTP 與 FastAPI 起步,走到資料庫、安全、認證、測試、前端整合、部署,最後用「預約管理系統」把所有東西串起來。整個系列固定在 Python 3.13、FastAPI 0.116、Pydantic 2.11、SQLModel 0.0.24、uvicorn 0.35、httpx 0.28 這幾個 2025 年 7 月的主流版本,並以「CPU 可跑」為主要執行需求。今天的完整實作雖然只有三個端點,但已經展示「定義 → 啟動 → 測試」這個後端開發的基本迴圈,後續章節會逐一展開。
結語
今天我們站在系列的最開頭,把地圖畫完卻還沒出發。明天,我們會把支撐整趟旅程的工具鏈一次建好:uv 管理環境與套件、VS Code 與它的 Python 擴充套件、Git 與分支策略、專案目錄結構與 pyproject.toml 的標準寫法。環境沒弄好,後面的範例都跑不動,所以 Web Day 2 是整個系列最重要的「地基」篇章之一,請預留一段完整的時間跟著做。
延伸資源
- FastAPI 官方教學(2025-07):
https://fastapi.tiangolo.com/ - Pydantic v2 官方教學(2025-07):
https://docs.pydantic.dev/latest/ - SQLModel 官方教學(2025-07):
https://sqlmodel.tiangolo.com/ - uv 官方說明(0.7,2025):
https://docs.astral.sh/uv/ - uvicorn 官方介紹(0.35,2025):
https://www.uvicorn.org/ - httpx 官方教學(0.28,2025):
https://www.python-httpx.org/
留言
張貼留言