2025年6月18日 星期三

從零開始:使用 FastAPI、MySQL 和 Docker 打造你的第一個電商購物車應用

從零開始:使用 FastAPI、MySQL 和 Docker 打造你的第一個電商購物車應用

引言:

哈囉,各位程式碼愛好者!你是否曾經想過要打造一個自己的網路應用程式,卻不知道從何開始?特別是對 Python 有興趣的初學者,常常會被複雜的 Web 框架和部署流程嚇到。別擔心!今天,我們將一起踏上一個有趣的旅程,使用 Python 最潮的 Web 框架 FastAPI,結合強大的 MySQL 數據庫,並透過 Docker 讓部署變得輕而易舉,一步步打造一個簡單卻功能齊全的電商購物車應用。

這個應用不只包含後端 API,還有一個簡單的 HTML 前端頁面,讓你直接看到成果!準備好了嗎?讓我們開始吧!

專案 GitHub 連結:https://github.com/BpsEason/ECommerceFastAPI.git

你會學到什麼?

  • FastAPI 基礎: 建立 RESTful API 端點、資料驗證。
  • 非同步程式設計: 如何在 Python 中處理高併發請求。
  • 數據庫操作: 使用 SQLAlchemy 和 AsyncMy 進行非同步 MySQL 數據庫交互。
  • 身份驗證: 實作 JWT (JSON Web Token) 認證機制。
  • 容器化部署: 使用 Docker 和 Docker Compose 快速部署整個應用。
  • 前端互動: 基礎 HTML/CSS/JavaScript 與後端 API 互動。
  • 單元測試: 使用 Pytest 確保 API 的正確性。

專案概覽:

我們的電商購物車應用將包含以下幾個部分:

  1. 後端 API (FastAPI): 提供商品列表、用戶註冊/登入、購物車管理(新增、更新、刪除、查看)等功能。
  2. 數據庫 (MySQL): 儲存用戶、商品和購物車資料。
  3. 前端 (HTML/CSS/JS): 簡單的商品展示頁和購物車頁面,透過 JavaScript 與後端 API 互動。
  4. 容器化 (Docker): 將所有服務打包成獨立的容器,方便部署和管理。

第一步:環境準備 (Docker 是你的好幫手!)

在開始寫程式之前,我們需要準備好開發環境。最簡單的方法就是使用 Docker。

  1. 安裝 Docker 和 Docker Compose:

    • 請訪問 Docker 官網,下載並安裝適用於你作業系統的 Docker Desktop。安裝完成後,Docker Compose 也會一併安裝。
  2. 克隆專案程式碼:

    • 我們會從 GitHub 下載這個專案的原始碼。打開你的終端機 (Terminal) 或命令提示字元 (Command Prompt),輸入:
      Bash
      git clone https://github.com/BpsEason/ECommerceFastAPI.git
      cd ECommerceFastAPI
      
    • 小提示: 如果你沒有安裝 Git,也可以直接從 GitHub 頁面下載 ZIP 壓縮檔並解壓縮。
  3. 設定 JWT 密鑰:

    • JWT (JSON Web Token) 是一種用於身份驗證的開放標準。我們需要一個安全的密鑰來加密和解密它。在專案根目錄下,創建一個名為 .env 的檔案,並在其中添加一行(請將 your-secure-jwt-secret-key 替換為你自己的隨機字串,你可以使用 openssl rand -hex 32 來生成一個):
      JWT_SECRET=你自己的安全密鑰,越長越複雜越好!
      
      例如:JWT_SECRET=f3a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1

第二步:啟動你的應用程式 (Docker Compose 一鍵搞定!)

現在,神奇的時刻來了!有了 Docker Compose,你只需要一個命令,就能啟動所有服務:

Bash
docker-compose up --build
  • docker-compose up:啟動 docker-compose.yaml 中定義的所有服務。
  • --build:如果服務的 Dockerfile 或其依賴有更新,會重新構建 Docker 映像。

第一次運行時,Docker 會下載所需的映像檔並構建你的應用程式,這可能需要一些時間。請耐心等待,直到看到類似 api_1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) 的訊息,這表示你的後端 API 已經啟動。

重要提示:數據庫初始化!

我們的 FastAPI 應用程式在啟動時,會自動幫你在 ecommerce 數據庫中創建 products、users 和 cart_items 這幾個表格。所以你通常不需要手動創建表格。

如果你想為應用程式添加一些初始商品數據,可以這樣做:

  1. 打開另一個終端機視窗,進入專案目錄。
  2. 連接到 MySQL 數據庫容器:
    Bash
    docker-compose exec db mysql -u root -prootpassword
    
  3. 在 MySQL 命令列中,執行以下 SQL 語句插入一些商品:
    SQL
    USE ecommerce;
    INSERT INTO products (name, price, stock) VALUES
    ('Python 初學者入門套件', 199.99, 100),
    ('FastAPI 實戰指南', 29.50, 50),
    ('Docker 精通之路', 45.00, 75);
    

第三步:探索你的應用程式

現在,你的電商應用已經在運行了!

  • 前端介面: 打開你的網頁瀏覽器,訪問 http://localhost:8080。你應該會看到一個簡單的商品列表頁面。你可以嘗試註冊新用戶、登入,然後將商品加入購物車並查看。

  • API 文件 (Swagger UI): 訪問 http://localhost:8000/docs。你會看到由 FastAPI 自動生成的互動式 API 文件。你可以在這裡測試所有的 API 端點,包括註冊、登入、查看商品、購物車操作等。

第四步:程式碼導覽 (為初學者設計)

讓我們簡單看看專案中幾個重要的檔案,了解它們是如何協同工作的。

1. main.py (後端 API)

這是我們 FastAPI 後端的核心程式碼。

Python
# ... (省略部分導入和設定)

# 數據庫模型 - 就像數據庫中的表格藍圖
class Product(Base):
    __tablename__ = "products"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(100), index=True)
    price = Column(Float)
    stock = Column(Integer)

# Pydantic 模型 - 用於定義 API 請求和回應的資料格式
class UserCreate(BaseModel):
    username: str
    password: str

# 註冊用戶 API 端點
@app.post("/register", summary="Register a new user")
async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
    # 這裡處理用戶註冊邏輯,包括密碼加密和存入數據庫
    # ...
    return {"message": "User created successfully"}

# 獲取所有商品 API 端點
@app.get("/products", response_model=List[ProductResponse], summary="Get all products")
async def get_products(db: AsyncSession = Depends(get_db)):
    # 從數據庫查詢所有商品
    # ...
    return result.scalars().all()

# ... (其他 API 端點,如 /token, /cart, PATCH /cart/{item_id}, DELETE /cart/{item_id})
  • FastAPI 輕量級、高效能的 Python Web 框架,用於構建 API。
  • SQLAlchemy Python 的 ORM (Object Relational Mapper),讓我們可以用 Python 物件的方式來操作數據庫,而不是直接寫 SQL 語句。
  • asyncmy SQLAlchemy 的異步 MySQL 驅動。
  • Pydantic 用於資料驗證和設定管理。它確保 API 接收到的資料符合預期格式,並自動生成 API 文件。
  • JWTpasslib 用於實現安全的用戶身份驗證 (JWT) 和密碼加密 (bcrypt)。
  • asyncawait Python 的異步程式設計語法,讓程式在等待數據庫響應時可以同時處理其他請求,大大提高了應用程式的併發處理能力。
  • @app.post, @app.get 等裝飾器: FastAPI 用它們來定義不同的 HTTP 方法和路徑。
  • Depends(get_db) / Depends(get_current_user) 這是 FastAPI 的「依賴注入」機制。它會自動處理數據庫連接和用戶認證,讓你的 API 函數保持簡潔。

2. requirements.txt

這個檔案列出了我們的 Python 專案所需的所有第三方庫及其版本。當 Docker 構建 API 服務時,會根據這個檔案來安裝依賴。

fastapi>=0.110.0
uvicorn>=0.28.0
sqlalchemy==2.0.23
asyncmy==0.2.9
python-jose==3.3.0
passlib[bcrypt]==1.7.4
pytest==7.4.0
pytest-asyncio==0.23.0
httpx==0.23.0
slowapi==0.1.9

3. docker-compose.yaml

這是 Docker Compose 的配置文件,它定義了我們應用程式的所有服務 (API、數據庫、前端) 以及它們之間的關係。

YAML
version: '3.8'
services:
  api: # 後端 FastAPI 服務
    build: . # 從當前目錄的 Dockerfile 構建
    ports:
      - "8000:8000" # 將容器的 8000 端口映射到主機的 8000 端口
    depends_on:
      - db # 依賴於 db 服務先啟動
    env_file:
      - ./.env # 從 .env 檔案加載環境變數 (例如 JWT_SECRET)
    volumes:
      - .:/app # 將當前目錄掛載到容器的 /app 目錄,方便程式碼修改後即時生效

  db: # MySQL 數據庫服務
    image: mysql:8.0 # 使用 MySQL 8.0 官方映像
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: ecommerce
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - db-data:/var/lib/mysql # 持久化數據,避免容器刪除後數據丟失

  frontend: # 前端 Nginx 服務
    build: ./frontend # 從 frontend 目錄的 Dockerfile 構建
    ports:
      - "8080:80" # 將容器的 80 端口 (Nginx 默認端口) 映射到主機的 8080 端口
    depends_on:
      - api # 依賴於 api 服務先啟動

volumes:
  db-data: # 定義一個用於數據庫持久化的命名 Volume
  • services 定義了三個獨立的服務:api (你的 FastAPI 應用)、db (MySQL 數據庫) 和 frontend (Nginx 靜態檔案伺服器)。
  • build 告訴 Docker Compose 如何構建該服務的 Docker 映像。. 表示在當前服務的上下文中查找 Dockerfile
  • ports 將容器內部的端口映射到你的主機端口,這樣你就可以通過主機訪問這些服務。
  • depends_on 指定服務的啟動順序。例如,api 服務會在 db 服務啟動後才啟動。
  • environment 為容器設置環境變數。
  • volumes 用於數據持久化。db-data:/var/lib/mysql 會將 MySQL 數據存儲在 Docker 管理的 db-data Volume 中,這樣即使容器被刪除,數據也不會丟失。.:/app 將主機上的專案程式碼同步到容器中,方便開發時的即時更新。

4. index.htmlcart.html (前端介面)

這些是我們簡單的靜態 HTML 頁面,它們使用 JavaScript 來與後端 API 互動。

  • fetch() API: JavaScript 的 fetch() 函數用於向後端發送 HTTP 請求(例如,獲取商品列表、將商品添加到購物車、登入等)。
  • localStorage 用於在瀏覽器中存儲用戶的 JWT token,以便在後續請求中進行身份驗證。

<!-- end list -->

HTML
<script>
    let token = localStorage.getItem('jwt_token'); // 從本地存儲獲取 token

    // 獲取商品列表的函數
    async function fetchProducts() {
        const response = await fetch('http://localhost:8000/products'); // 向後端 API 發送請求
        const products = await response.json();
        // ... (將商品顯示在頁面上)
    }

    // 將商品添加到購物車的函數
    async function addToCart(productId) {
        if (!token) {
            alert('請先登入'); // 如果沒有 token,提示用戶登入
            // ...
            return;
        }
        const quantity = document.getElementById(`quantity-${productId}`).value;
        const response = await fetch('http://localhost:8000/cart', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${token}` // 在請求頭中發送 token
            },
            body: JSON.stringify({ product_id: productId, quantity: parseInt(quantity) })
        });
        // ... (處理回應,例如彈出提示)
    }

    // ... (登入、註冊、登出等函數)
</script>

5. test_api.py (測試程式碼)

這是一個使用 pytest 框架編寫的測試檔案,用於自動化測試我們的 API 端點是否按預期工作。

Python
import pytest
from fastapi.testclient import TestClient
from main import app, get_db, Base, Product, User, CartItem # 導入必要的模組和模型
# ... (數據庫設置和覆寫依賴)

@pytest.fixture(autouse=True)
async def setup_database():
    # 在每次測試前後,清理並重新創建數據庫表格,確保測試隔離
    async with main.engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    async with main.engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)

@pytest.mark.asyncio
async def test_register_user():
    response = client.post("/register", json={"username": "testuser", "password": "testpassword"})
    assert response.status_code == 200
    assert response.json() == {"message": "User created successfully"}

@pytest.mark.asyncio
async def test_login_success():
    # ... (測試登入成功)
    pass

# ... (其他測試函數,例如測試獲取商品、添加購物車、更新購物車、刪除購物車)
  • pytest 強大的 Python 測試框架。
  • fastapi.testclient.TestClient 允許你在不啟動實際伺服器的情況下,直接對 FastAPI 應用進行測試。
  • @pytest.mark.asynciopytest 能夠運行異步的測試函數。
  • Fixture (夾具): setup_database 是一個 fixture,它在每個測試函數運行前後執行代碼(這裡是用於數據庫的設置和清理),確保測試環境的純淨。

第五步:運行測試 (確保一切正常!)

在專案目錄下,執行以下命令來運行你的 API 測試:

Bash
docker-compose exec api pytest tests/test_api.py -v
  • docker-compose exec api:在正在運行的 api 服務容器內部執行命令。
  • pytest tests/test_api.py -v:運行 tests/test_api.py 檔案中的所有測試,-v 參數會顯示詳細的測試結果。

如果一切設置正確,你應該會看到所有測試都通過了!

第六步:一些進階思考 (給未來的你!)

  1. 錯誤處理優化: 目前前端直接 alert() 錯誤訊息。在實際專案中,你可以設計更友好的錯誤提示 UI,例如在頁面上方顯示一個警告條。
  2. 前端路由: 對於更複雜的單頁應用程式 (SPA),你可以考慮使用 React、Vue 或 Angular 等前端框架,搭配前端路由來管理頁面跳轉,而不是直接使用多個 HTML 檔案。
  3. 環境變數管理: 目前 JWT_SECRET 已在 .env 中管理。對於前端 API URL 等,也可以考慮通過環境變數或構建工具來動態配置,使其在不同部署環境下更靈活。
  4. 數據庫遷移: 隨著專案的發展,數據庫結構可能會改變。學習使用數據庫遷移工具 (如 Alembic 配合 SQLAlchemy) 將是管理數據庫變化的好方法。
  5. 部署到雲端: 當你準備好將應用上線時,可以探索將 Docker 容器部署到雲平台 (如 AWS ECS, Google Cloud Run, Azure Container Instances) 的方法。

結語:

恭喜你!你已經成功地使用 FastAPI、MySQL 和 Docker 構建並運行了一個基本的電商購物車應用。這不僅讓你接觸到了現代 Web 開發的核心技術,也為你未來的學習打下了堅實的基礎。

這只是一個開始,程式設計的世界充滿了無限可能。繼續探索,享受程式碼帶來的樂趣吧!如果你有任何問題或想法,歡迎在下方留言交流!


沒有留言:

張貼留言

網誌存檔