วิธีสร้าง REST API ด้วย FastAPI Python แบบครบจบ

ในยุคที่เทคโนโลยีขับเคลื่อนโลกอย่างรวดเร็ว การเชื่อมต่อและแลกเปลี่ยนข้อมูลระหว่างแอปพลิเคชันและระบบต่าง ๆ ได้กลายเป็นหัวใจสำคัญของการพัฒนาซอฟต์แวร์ยุคใหม่ครับ และ REST API ก็คือพระเอกที่เข้ามาตอบโจทย์นี้ได้อย่างสมบูรณ์แบบ วันนี้เราจะพาทุกท่านดำดิ่งสู่โลกของการสร้าง REST API ด้วย FastAPI เฟรมเวิร์ก Python ที่กำลังมาแรงที่สุดตัวหนึ่ง ด้วยประสิทธิภาพที่เหนือกว่า, การรองรับ Asynchronous เต็มรูปแบบ, การใช้งาน Type Hints ที่ยอดเยี่ยม และการสร้างเอกสาร API โดยอัตโนมัติ ทำให้ FastAPI เป็นตัวเลือกอันดับต้น ๆ สำหรับนักพัฒนาที่ต้องการสร้าง API ที่รวดเร็ว, แข็งแกร่ง และดูแลรักษาง่ายครับ บทความนี้จะครอบคลุมทุกแง่มุม ตั้งแต่การติดตั้งพื้นฐานไปจนถึงการสร้างระบบที่ซับซ้อนพร้อมการเชื่อมต่อฐานข้อมูลและการยืนยันตัวตน เพื่อให้คุณสามารถสร้าง REST API ด้วย FastAPI ได้อย่างครบจบในที่เดียว มาเริ่มกันเลยครับ!

สารบัญ

บทนำ: ทำไมต้อง FastAPI?

ก่อนที่เราจะลงมือสร้าง API กัน เรามาทำความเข้าใจกันก่อนว่าทำไม FastAPI ถึงเป็นตัวเลือกที่น่าสนใจสำหรับการพัฒนา REST API ในปัจจุบันครับ

FastAPI คืออะไร?

FastAPI คือเว็บเฟรมเวิร์กสำหรับ Python ที่ใช้สร้าง RESTful API โดยเฉพาะ สร้างขึ้นบนพื้นฐานของ Starlette (สำหรับเว็บพาร์ท) และ Pydantic (สำหรับ Data Validation และ Serialization) ทำให้ FastAPI มีความสามารถที่โดดเด่นหลายประการ:

  • ประสิทธิภาพสูง (High Performance): เป็นหนึ่งในเฟรมเวิร์ก Python ที่เร็วที่สุด โดยมีประสิทธิภาพเทียบเท่ากับ Node.js และ Go ครับ
  • เพิ่มผลผลิตให้กับการพัฒนา (Increased Developer Productivity): ด้วยการใช้ Type Hints ของ Python และ Pydantic ทำให้โค้ดมีความชัดเจน, ลดข้อผิดพลาด, และช่วยให้ IDE สามารถตรวจสอบโค้ด (Autocompletion) ได้ดีเยี่ยมครับ
  • รองรับ Asynchronous (Async/Await) เต็มรูปแบบ: เหมาะสำหรับการสร้าง API ที่ต้องมีการ I/O Bound Operations สูง เช่น การอ่าน/เขียนฐานข้อมูล, การเรียก API ภายนอก โดยไม่บล็อกการทำงานหลักของเซิร์ฟเวอร์ครับ
  • เอกสาร API อัตโนมัติ (Automatic API Documentation): FastAPI สร้างเอกสาร API แบบ Interactive โดยใช้มาตรฐาน OpenAPI (Swagger UI) และ ReDoc ให้คุณโดยอัตโนมัติ ทำให้การทดสอบและทำความเข้าใจ API ง่ายขึ้นมากครับ
  • การตรวจสอบข้อมูลอัตโนมัติ (Automatic Data Validation): Pydantic ช่วยให้คุณสามารถกำหนดโครงสร้างข้อมูลที่เข้ามาและออกไปจาก API ได้อย่างง่ายดาย พร้อมการตรวจสอบข้อมูลและแปลงชนิดข้อมูลให้โดยอัตโนมัติครับ
  • ความปลอดภัย (Security): มีเครื่องมือสำหรับจัดการความปลอดภัย เช่น การทำ OAuth2, JWT และ HTTP Basic Authentication ในตัวครับ

ด้วยคุณสมบัติเหล่านี้ ทำให้ FastAPI ไม่ได้เป็นเพียงแค่เครื่องมือ แต่เป็นโซลูชันที่ช่วยให้การสร้าง API ที่ซับซ้อนเป็นเรื่องง่ายและมีประสิทธิภาพอย่างไม่น่าเชื่อครับ

ความเข้าใจพื้นฐานเกี่ยวกับ REST API

ก่อนจะลงมือเขียนโค้ด เรามาทบทวนแนวคิดพื้นฐานของ REST API กันสักหน่อยนะครับ การเข้าใจหลักการเหล่านี้จะช่วยให้เราออกแบบและสร้าง API ที่มีประสิทธิภาพและเป็นมาตรฐานครับ

REST (Representational State Transfer) คือสถาปัตยกรรมซอฟต์แวร์สำหรับระบบเครือข่าย โดยเน้นการสื่อสารแบบ Stateless ระหว่าง Client และ Server ผ่านโปรโตคอล HTTP ครับ

หลักการสำคัญของ REST:

  1. Client-Server: แยก Client (เช่น เว็บเบราว์เซอร์, แอปมือถือ) และ Server ออกจากกันอย่างชัดเจน Client จะส่งคำขอไปยัง Server และ Server จะส่งการตอบกลับมา
  2. Stateless: แต่ละคำขอจาก Client ไปยัง Server จะต้องมีข้อมูลที่จำเป็นทั้งหมดในการประมวลผลคำขอ โดย Server จะไม่เก็บข้อมูลสถานะของ Client ระหว่างคำขอแต่ละครั้งครับ
  3. Cacheable: การตอบกลับจาก Server สามารถระบุได้ว่าสามารถ Cache ได้หรือไม่ เพื่อปรับปรุงประสิทธิภาพการทำงานของ Client
  4. Layered System: Client ไม่จำเป็นต้องรู้ว่ากำลังเชื่อมต่อโดยตรงกับ Server หรือผ่าน Layer อื่น ๆ เช่น Load Balancer หรือ Proxy
  5. Uniform Interface: เป็นหลักการที่สำคัญที่สุด เพื่อให้ระบบมีความเรียบง่ายและเป็นมาตรฐาน โดยมีส่วนประกอบดังนี้:
    • Resources (ทรัพยากร): ทุกสิ่งทุกอย่างใน REST API จะถูกมองว่าเป็นทรัพยากร เช่น ผู้ใช้งาน, สินค้า, คำสั่งซื้อ โดยแต่ละทรัพยากรจะมี Unique Identifier (URI/URL) ครับ
    • Standard Methods (HTTP Methods): ใช้ HTTP Methods มาตรฐานในการดำเนินการกับทรัพยากร:
      • GET: ใช้สำหรับดึงข้อมูลทรัพยากร
      • POST: ใช้สำหรับสร้างทรัพยากรใหม่
      • PUT: ใช้สำหรับอัปเดตข้อมูลทรัพยากรที่มีอยู่ทั้งหมด
      • PATCH: ใช้สำหรับอัปเดตข้อมูลทรัพยากรบางส่วน
      • DELETE: ใช้สำหรับลบทรัพยากร
    • Self-descriptive Messages: ข้อความที่แลกเปลี่ยนระหว่าง Client และ Server ควรมีข้อมูลที่เพียงพอที่จะอธิบายวิธีการประมวลผลข้อความนั้น
    • HATEOAS (Hypermedia As The Engine Of Application State): เป็นหลักการที่ Client สามารถนำทางผ่าน API ได้โดยใช้ลิงก์ที่ Server ให้มาใน Response ครับ (ขั้นสูงขึ้นมาหน่อย)

HTTP Status Codes ที่พบบ่อย:

  • 200 OK: การร้องขอสำเร็จ
  • 201 Created: สร้างทรัพยากรใหม่สำเร็จ (มักใช้กับ POST)
  • 204 No Content: การร้องขอสำเร็จ แต่ไม่มีข้อมูลจะส่งกลับมา (มักใช้กับ DELETE)
  • 400 Bad Request: คำขอไม่ถูกต้อง
  • 401 Unauthorized: ไม่ได้รับอนุญาตให้เข้าถึง (ต้องมีการยืนยันตัวตน)
  • 403 Forbidden: ได้รับอนุญาตแล้ว แต่ไม่มีสิทธิ์เข้าถึงทรัพยากรนั้น ๆ
  • 404 Not Found: ไม่พบทรัพยากรที่ร้องขอ
  • 422 Unprocessable Entity: คำขอถูกต้องตามโครงสร้าง แต่ไม่สามารถประมวลผลได้ (เช่น Data Validation ล้มเหลว)
  • 500 Internal Server Error: ข้อผิดพลาดภายในเซิร์ฟเวอร์

การเข้าใจหลักการเหล่านี้จะทำให้เราออกแบบ API ที่สอดคล้องกับมาตรฐาน RESTful และใช้งานได้ง่ายขึ้นมากครับ

เตรียมความพร้อม: ติดตั้ง Python และ FastAPI

ขั้นแรกของการสร้าง REST API คือการเตรียมสภาพแวดล้อมในการพัฒนาครับ เราจะเริ่มต้นด้วยการติดตั้ง Python และสร้าง Virtual Environment เพื่อแยก Dependencies ของโปรเจกต์เราออกจากระบบหลักครับ

1. ติดตั้ง Python

ก่อนอื่น ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้ง Python 3.7+ (แนะนำ 3.9 หรือใหม่กว่า) บนเครื่องของคุณแล้วครับ

คุณสามารถตรวจสอบเวอร์ชัน Python ได้โดยเปิด Terminal (macOS/Linux) หรือ Command Prompt/PowerShell (Windows) แล้วพิมพ์:

python3 --version
# หรือบางระบบอาจใช้
python --version

หากยังไม่ได้ติดตั้ง หรือต้องการติดตั้งเวอร์ชันล่าสุด คุณสามารถดาวน์โหลดได้จาก เว็บไซต์ทางการของ Python ครับ สำหรับผู้ใช้งาน macOS/Linux อาจพิจารณาใช้เครื่องมืออย่าง pyenv เพื่อจัดการหลายเวอร์ชันของ Python ได้อย่างง่ายดายครับ

2. สร้างและเปิดใช้งาน Virtual Environment

การใช้ Virtual Environment เป็นสิ่งสำคัญมากในการพัฒนา Python เพื่อป้องกันปัญหา Dependency Conflict ระหว่างโปรเจกต์ต่าง ๆ ครับ

สร้างโฟลเดอร์สำหรับโปรเจกต์ของคุณ:

mkdir my-fastapi-app
cd my-fastapi-app

สร้าง Virtual Environment:

python3 -m venv venv

เปิดใช้งาน Virtual Environment:

  • สำหรับ macOS/Linux:
    source venv/bin/activate
  • สำหรับ Windows (Command Prompt):
    venv\Scripts\activate.bat
  • สำหรับ Windows (PowerShell):
    venv\Scripts\Activate.ps1

เมื่อเปิดใช้งานแล้ว คุณจะเห็น (venv) นำหน้าบรรทัดคำสั่ง ซึ่งหมายความว่าคุณกำลังอยู่ใน Virtual Environment แล้วครับ

3. ติดตั้ง FastAPI และ Uvicorn

FastAPI ต้องการ ASGI server (เช่น Uvicorn) เพื่อรันแอปพลิเคชันของเราครับ

pip install "fastapi[all]" uvicorn

คำสั่ง "fastapi[all]" จะติดตั้ง FastAPI พร้อมกับ Dependencies ที่แนะนำทั้งหมด เช่น Pydantic, python-multipart (สำหรับการจัดการฟอร์มข้อมูล), jinja2 (สำหรับ templating) และอื่น ๆ ซึ่งสะดวกมากสำหรับเริ่มต้นครับ

ตอนนี้คุณก็พร้อมที่จะสร้าง REST API ตัวแรกด้วย FastAPI แล้วครับ!

เริ่มต้นโปรเจกต์ FastAPI แรกของคุณ

เมื่อเราเตรียมสภาพแวดล้อมเรียบร้อยแล้ว ก็ถึงเวลาสร้างแอปพลิเคชัน FastAPI ตัวแรกของเราครับ

1. สร้างไฟล์ main.py

ในโฟลเดอร์โปรเจกต์ my-fastapi-app ของคุณ ให้สร้างไฟล์ชื่อ main.py และใส่โค้ดต่อไปนี้ครับ

# main.py
from fastapi import FastAPI

# สร้าง instance ของ FastAPI
# app คือ object หลักของ FastAPI ที่เราจะใช้ในการกำหนด routing และ logic ต่างๆ
app = FastAPI()

# กำหนด Path Operation Decorator สำหรับ HTTP GET request ที่ path "/"
# เมื่อมี GET request มายัง root path, ฟังก์ชัน read_root() จะถูกเรียกใช้งาน
@app.get("/")
async def read_root():
    """
    Endpoint สำหรับ root path ที่จะส่งคืนข้อความต้อนรับ
    """
    # FastAPI จะแปลง Python dictionary นี้ให้เป็น JSON response โดยอัตโนมัติ
    return {"message": "Hello, World! Welcome to FastAPI."}

# ตัวอย่างอีกหนึ่ง endpoint สำหรับทดสอบ
@app.get("/hello/{name}")
async def say_hello(name: str):
    """
    Endpoint ที่รับชื่อเป็น Path Parameter และส่งคืนข้อความทักทาย
    """
    return {"message": f"Hello, {name}! How are you today?"}

อธิบายโค้ด:

  • from fastapi import FastAPI: นำเข้าคลาส FastAPI จากไลบรารี fastapi ครับ
  • app = FastAPI(): สร้างอินสแตนซ์ของ FastAPI ซึ่งเป็น object หลักที่เราจะใช้ในการสร้าง API ครับ
  • @app.get("/"): นี่คือ Path Operation Decorator ครับ มันบอก FastAPI ว่าฟังก์ชันที่อยู่ใต้ Decorator นี้ควรจะถูกเรียกเมื่อมี HTTP GET request เข้ามาที่ Path "/" (root path) ครับ
  • async def read_root():: ฟังก์ชันที่ถูกเรียกเมื่อ Path Operation นี้ถูกทริกเกอร์ FastAPI ถูกออกแบบมาให้ทำงานแบบ Asynchronous ได้อย่างเต็มที่ ดังนั้นเราจึงใช้ async def ครับ แม้ว่าในกรณีนี้จะไม่ได้มีการรอ (await) อะไรก็ตาม แต่ก็เป็นแนวทางปฏิบัติที่ดีครับ
  • return {"message": "Hello, World! Welcome to FastAPI."}: ฟังก์ชันนี้จะส่งคืน Python Dictionary ซึ่ง FastAPI จะแปลงเป็น JSON Response ให้โดยอัตโนมัติครับ
  • @app.get("/hello/{name}"): เป็นตัวอย่างการรับค่า name เป็น Path Parameter ซึ่งจะอธิบายเพิ่มเติมในส่วนถัดไปครับ

2. รันแอปพลิเคชันด้วย Uvicorn

เปิด Terminal (ที่ Virtual Environment ถูก activate อยู่) และรันคำสั่งต่อไปนี้ครับ

uvicorn main:app --reload

อธิบายคำสั่ง:

  • uvicorn: คือ ASGI server ที่เราติดตั้งไปครับ
  • main:app: บอก Uvicorn ว่าให้หา object app ภายในไฟล์ main.py ครับ
  • --reload: โหมดนี้จะทำให้ Uvicorn ตรวจจับการเปลี่ยนแปลงในโค้ดของคุณและรีโหลดเซิร์ฟเวอร์โดยอัตโนมัติ ซึ่งสะดวกมากสำหรับการพัฒนาครับ

คุณจะเห็นข้อความประมาณนี้ใน Terminal ครับ:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [xxxxx]
INFO:     Started server process [xxxxx]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

3. ทดสอบ API ของคุณ

เปิดเว็บเบราว์เซอร์ของคุณแล้วไปที่:

คุณควรจะเห็น JSON response ในเบราว์เซอร์ครับ

และที่ยอดเยี่ยมกว่านั้น FastAPI ยังสร้างเอกสาร API โดยอัตโนมัติให้คุณด้วยครับ ลองไปที่:

คุณจะเห็นหน้าเอกสาร API แบบ Interactive ที่คุณสามารถทดสอบ Endpoint ของคุณได้โดยตรงจากหน้านั้นเลยครับ นี่คือหนึ่งในคุณสมบัติที่ทรงพลังของ FastAPI ที่ช่วยประหยัดเวลาและลดความซับซ้อนในการทำเอกสารได้อย่างมหาศาลครับ

การสร้าง Endpoint พื้นฐาน (GET)

เราได้สร้าง Endpoint พื้นฐานไปแล้วในส่วนที่แล้ว แต่เพื่อความเข้าใจที่ลึกซึ้งขึ้น เราจะมาดูรายละเอียดเกี่ยวกับการสร้าง Path Operation สำหรับ HTTP GET request กันครับ

Path Operation เป็นการเชื่อมโยง HTTP Method (GET, POST, PUT, DELETE, etc.) กับ URL Path (เช่น /items, /users) เข้ากับฟังก์ชัน Python ที่จะทำงานเมื่อมีการร้องขอเข้ามาครับ

Path Operation Decorator

การสร้าง Path Operation ใน FastAPI ทำได้ง่ายมากด้วย Decorator ครับ

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"message": "Welcome to FastAPI! This is the root endpoint."}

@app.get("/items/")
async def read_items():
    """
    Endpoint สำหรับดึงรายการสินค้าทั้งหมด
    """
    return [
        {"item_id": 1, "name": "Laptop", "price": 1200},
        {"item_id": 2, "name": "Mouse", "price": 25},
        {"item_id": 3, "name": "Keyboard", "price": 75},
    ]

@app.get("/users/")
async def read_users():
    """
    Endpoint สำหรับดึงรายการผู้ใช้งาน
    """
    return [
        {"user_id": 1, "username": "alice"},
        {"user_id": 2, "username": "bob"},
    ]

# คุณสามารถเพิ่ม Metadata ให้กับ Path Operation ได้
@app.get("/health", tags=["Monitoring"], summary="Check API Health Status")
async def health_check():
    """
    ตรวจสอบสถานะการทำงานของ API ว่าพร้อมใช้งานหรือไม่
    """
    return {"status": "ok", "message": "API is up and running."}

อธิบายโค้ดเพิ่มเติม:

  • @app.get("/items/"): กำหนดว่าเมื่อมี HTTP GET request มายัง /items/ ฟังก์ชัน read_items จะถูกเรียกใช้ครับ
  • @app.get("/users/"): เช่นเดียวกัน สำหรับ /users/ ฟังก์ชัน read_users จะถูกเรียกใช้ครับ
  • Metadata for Path Operations:
    • tags=["Monitoring"]: ใช้สำหรับจัดกลุ่ม Endpoint ในเอกสาร Swagger UI ครับ ทำให้ดูเป็นระเบียบมากขึ้น
    • summary="Check API Health Status": เป็นคำสรุปสั้น ๆ ที่จะแสดงในเอกสาร API
    • """Docstring""": Docstring ในฟังก์ชันจะถูกใช้เป็น description ของ Endpoint ในเอกสาร API ครับ

การสร้าง Endpoint ด้วย FastAPI นั้นตรงไปตรงมาและเข้าใจง่ายมากครับ คุณเพียงแค่เลือก HTTP Method ที่เหมาะสมกับ Path ของคุณ แล้วกำหนดฟังก์ชันที่จะจัดการ Logic นั้น ๆ ได้เลย

การจัดการ Path Parameters

บ่อยครั้งที่เราต้องการดึงข้อมูลทรัพยากรเฉพาะ เช่น ดึงข้อมูลสินค้าชิ้นเดียวตาม ID ของมัน Path Parameters เข้ามามีบทบาทตรงนี้ครับ

Path Parameters คือค่าที่ถูกฝังอยู่ใน URL Path โดยตรง โดยจะถูกระบุด้วยเครื่องหมายวงเล็บปีกกา {} ใน Path String ครับ

การใช้งาน Path Parameters

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI

app = FastAPI()

# ... (โค้ดเก่า) ...

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    """
    Endpoint สำหรับดึงข้อมูลสินค้าตาม ID
    FastAPI จะทำการแปลง `item_id` จาก string ใน URL เป็น integer ให้โดยอัตโนมัติ
    """
    return {"item_id": item_id, "name": f"Item {item_id}", "description": "This is a sample item."}

@app.get("/users/{user_id}/orders/{order_id}")
async def read_user_order(user_id: int, order_id: int):
    """
    Endpoint สำหรับดึงข้อมูลคำสั่งซื้อของ user คนใดคนหนึ่ง
    แสดงให้เห็นการใช้งานหลาย Path Parameters
    """
    return {"user_id": user_id, "order_id": order_id, "detail": f"Order {order_id} for User {user_id}"}

# Path Parameter พร้อม Type Hint ที่กำหนดค่าเริ่มต้น
@app.get("/products/{product_name}")
async def get_product_by_name(product_name: str, q: str | None = None):
    """
    ตัวอย่าง Path Parameter ที่เป็น string และ Query Parameter
    """
    if q:
        return {"product_name": product_name, "query": q}
    return {"product_name": product_name}

# Path Parameter พร้อม enum (ตัวอย่างขั้นสูงขึ้น)
from enum import Enum

class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    """
    Endpoint ที่รับ Path Parameter เป็นค่าจาก Enum
    FastAPI จะตรวจสอบว่าค่าที่ส่งมาตรงกับ Enum ที่กำหนดหรือไม่
    """
    if model_name is ModelName.alexnet:
        return {"model_name": model_name, "message": "Deep Learning FTW!"}
    if model_name.value == "lenet":
        return {"model_name": model_name, "message": "LeNet is a classic!"}
    return {"model_name": model_name, "message": "Have some residuals"}

อธิบายโค้ด:

  • @app.get("/items/{item_id}"): เรากำหนด Path Parameter ชื่อ item_id ใน URL Path ครับ
  • async def read_item(item_id: int)::
    • เราประกาศ item_id เป็นพารามิเตอร์ของฟังก์ชัน read_item ครับ
    • สิ่งสำคัญคือการใช้ Type Hint (item_id: int) ครับ FastAPI จะใช้ Type Hint นี้ในการ:
      • ตรวจสอบข้อมูล (Data Validation): หากค่าที่ส่งมาใน URL ไม่สามารถแปลงเป็น int ได้ FastAPI จะส่ง HTTP 422 Unprocessable Entity กลับไปโดยอัตโนมัติครับ
      • แปลงชนิดข้อมูล (Data Conversion): ค่าที่ได้จาก URL จะเป็น String เสมอ แต่ FastAPI จะแปลงเป็น int ให้คุณโดยอัตโนมัติก่อนส่งเข้าฟังก์ชันครับ
      • สร้างเอกสาร (Documentation): Type Hint จะถูกนำไปใช้ในเอกสาร Swagger UI เพื่อบอกว่าพารามิเตอร์นี้คาดหวังข้อมูลชนิดใดครับ
  • @app.get("/users/{user_id}/orders/{order_id}"): แสดงให้เห็นว่าเราสามารถมี Path Parameters ได้หลายตัวใน Path เดียวกันครับ FastAPI จะจัดการ Type Hint และการแปลงข้อมูลให้ทั้งหมด
  • Path Parameter with Enum:
    • เราสามารถใช้ Enum จาก Python เพื่อจำกัดค่าที่เป็นไปได้สำหรับ Path Parameter ได้ครับ
    • FastAPI จะตรวจสอบว่าค่าที่ส่งมาตรงกับสมาชิกของ ModelName Enum หรือไม่ หากไม่ตรงก็จะส่ง Error 422 กลับไปครับ

Path Parameters ทำให้ API ของเรามีความยืดหยุ่นและเข้าถึงทรัพยากรเฉพาะได้อย่างมีประสิทธิภาพครับ ด้วย Type Hints, FastAPI ช่วยให้เรามั่นใจได้ว่าข้อมูลที่รับเข้ามานั้นถูกต้องตามชนิดที่เราต้องการครับ

การจัดการ Query Parameters

นอกจากการระบุข้อมูลใน Path ด้วย Path Parameters แล้ว เรายังสามารถส่งข้อมูลเพิ่มเติมเพื่อกรอง, จัดเรียง, หรือกำหนดเงื่อนไขในการดึงข้อมูลได้ด้วย Query Parameters ครับ

Query Parameters จะอยู่ต่อท้าย URL หลังจากเครื่องหมาย ? และประกอบด้วยคู่ของ key=value คั่นด้วย & เช่น /items?skip=0&limit=10

การใช้งาน Query Parameters

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI, Query
from typing import Optional, List

app = FastAPI()

# ... (โค้ดเก่า) ...

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    """
    Endpoint สำหรับดึงรายการสินค้าพร้อม Query Parameters สำหรับการแบ่งหน้า (Pagination)
    - `skip`: จำนวนรายการที่จะข้ามไป (ค่าเริ่มต้น 0)
    - `limit`: จำนวนรายการสูงสุดที่จะดึงมา (ค่าเริ่มต้น 10)
    """
    all_items = [
        {"item_id": 1, "name": "Laptop", "price": 1200},
        {"item_id": 2, "name": "Mouse", "price": 25},
        {"item_id": 3, "name": "Keyboard", "price": 75},
        {"item_id": 4, "name": "Monitor", "price": 300},
        {"item_id": 5, "name": "Webcam", "price": 50},
    ]
    return all_items[skip : skip + limit]

@app.get("/products/")
async def search_products(q: Optional[str] = None, price_min: float = 0.0, price_max: Optional[float] = None):
    """
    Endpoint สำหรับค้นหาสินค้าพร้อม Query Parameters ที่ซับซ้อนขึ้น
    - `q`: คำค้นหา (Optional)
    - `price_min`: ราคาขั้นต่ำ (ค่าเริ่มต้น 0.0)
    - `price_max`: ราคาขั้นสูงสุด (Optional)
    """
    results = []
    if q:
        results.append({"product_name": f"Product with query: {q}"})
    if price_min > 0:
        results.append({"min_price_filter": price_min})
    if price_max:
        results.append({"max_price_filter": price_max})
    if not results:
        return {"message": "No specific search criteria provided."}
    return results

# Query Parameter ที่บังคับ (Required Query Parameter)
@app.get("/users_search/")
async def search_users(keyword: str):
    """
    Endpoint สำหรับค้นหาผู้ใช้งานด้วย keyword ที่บังคับ
    """
    return {"message": f"Searching users with keyword: {keyword}"}

# Query Parameters ที่รับค่าได้หลายค่า (List of Query Parameters)
@app.get("/items_by_tags/")
async def get_items_by_tags(tags: List[str] = Query(None)):
    """
    Endpoint สำหรับดึงรายการสินค้าตาม tag ที่ระบุ (สามารถระบุได้หลาย tag)
    ตัวอย่าง: /items_by_tags/?tags=electronics&tags=gadgets
    """
    if tags:
        return {"tags": tags, "message": f"Items filtered by tags: {', '.join(tags)}"}
    return {"message": "Please provide one or more tags."}

อธิบายโค้ด:

  • async def read_items(skip: int = 0, limit: int = 10):
    • พารามิเตอร์ฟังก์ชันที่ไม่ได้เป็นส่วนหนึ่งของ Path จะถูกตีความว่าเป็น Query Parameters ครับ
    • เราใช้ Type Hint (skip: int, limit: int) เพื่อให้ FastAPI ตรวจสอบและแปลงชนิดข้อมูลให้ครับ
    • การกำหนดค่าเริ่มต้น (= 0, = 10) ทำให้ Query Parameter เหล่านี้เป็น Optional ครับ หาก Client ไม่ได้ส่งมา FastAPI จะใช้ค่าเริ่มต้นที่เรากำหนด
  • q: Optional[str] = None:
    • ใช้ Optional จากโมดูล typing (หรือ str | None ใน Python 3.10+) เพื่อระบุว่าพารามิเตอร์นี้เป็น Optional และค่าเริ่มต้นคือ None ครับ
    • FastAPI จะเข้าใจว่าถ้า Client ไม่ได้ส่ง Query Parameter q มา ก็จะใช้ค่า None ครับ
  • keyword: str:
    • หากเราไม่กำหนดค่าเริ่มต้นให้กับ Query Parameter (เช่น keyword: str โดยไม่มี = ...) FastAPI จะถือว่า Query Parameter นั้นเป็น Required ครับ
    • หาก Client ไม่ได้ส่ง keyword มา FastAPI จะส่ง HTTP 422 Unprocessable Entity กลับไปครับ
  • tags: List[str] = Query(None):
    • สำหรับการรับ Query Parameter ที่มีชื่อเดียวกันแต่ส่งมาหลายครั้ง (เช่น ?tags=electronics&tags=gadgets) เราสามารถใช้ Type Hint เป็น List[str] ได้ครับ
    • และใช้ Query(None) เพื่อระบุว่าพารามิเตอร์นี้เป็น Optional และหากไม่มีการส่งค่ามา จะเป็น List ว่าง หรือ None ขึ้นอยู่กับการกำหนด (ในที่นี้ None) ครับ
    • หากเราต้องการให้เป็น Required List เราสามารถใช้ tags: List[str] = Query(...) โดย ... หมายถึง Required ครับ

Query Parameters เป็นวิธีที่ยืดหยุ่นในการส่งข้อมูลเพิ่มเติมไปยัง API ของคุณ เพื่อควบคุมการดึงข้อมูลได้อย่างละเอียดครับ FastAPI ทำให้การจัดการ Query Parameters เหล่านี้ง่ายและมีประสิทธิภาพด้วยการผสานรวมกับ Type Hints และ Pydantic ครับ

การส่งข้อมูลด้วย Request Body (POST, PUT)

เมื่อเราต้องการสร้างทรัพยากรใหม่ (ด้วย POST) หรืออัปเดตทรัพยากรที่มีอยู่ (ด้วย PUT/PATCH) เรามักจะต้องส่งข้อมูลจำนวนมากจาก Client ไปยัง Server ครับ ข้อมูลเหล่านี้จะถูกบรรจุอยู่ใน Request Body ซึ่งมักจะอยู่ในรูปแบบ JSON ครับ

FastAPI ใช้ Pydantic ในการจัดการ Request Body ทำให้เราสามารถกำหนดโครงสร้างข้อมูลที่คาดหวังได้อย่างง่ายดาย พร้อมทั้งได้รับการตรวจสอบข้อมูล (Data Validation) และการแปลงชนิดข้อมูล (Serialization) โดยอัตโนมัติครับ

การใช้งาน Request Body ด้วย Pydantic Model

ก่อนอื่น เราต้องสร้าง Pydantic Model เพื่อกำหนดโครงสร้างข้อมูลของ Request Body ครับ

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from typing import List, Optional

app = FastAPI()

# Pydantic Model สำหรับ Item
# BaseModel เป็น base class ที่ Pydantic ใช้ในการสร้าง data models
class Item(BaseModel):
    # Field ที่ต้องการ (Required Field)
    name: str = Field(..., example="Laptop Pro") # ใช้ Field เพื่อเพิ่ม metadata เช่น example
    description: Optional[str] = Field(None, example="Powerful laptop for professionals.")
    price: float = Field(..., gt=0, description="Price must be greater than zero", example=1200.0) # gt=0 คือ greater than 0
    tax: Optional[float] = Field(None, example=0.1)

    # การเพิ่มตัวอย่างข้อมูลสำหรับเอกสาร Swagger UI
    class Config:
        schema_extra = {
            "example": {
                "name": "Smartphone X",
                "description": "The latest smartphone with advanced features.",
                "price": 999.99,
                "tax": 0.08,
            }
        }

# สมมติฐานว่าเรามีฐานข้อมูลเป็น List ของ Item objects
# ในแอปพลิเคชันจริงจะเชื่อมต่อกับฐานข้อมูลจริง
fake_db = []
next_item_id = 1

@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
    """
    Endpoint สำหรับสร้างสินค้าใหม่
    - รับ Item object ใน Request Body
    - คืน Item object ที่สร้างเสร็จแล้วพร้อม ID (ในกรณีนี้แค่จำลอง)
    """
    global next_item_id
    item_dict = item.dict() # แปลง Pydantic model เป็น dict
    item_dict["id"] = next_item_id
    fake_db.append(item_dict)
    next_item_id += 1
    return item_dict # FastAPI จะแปลง dict นี้กลับเป็น Item model (ตาม response_model) และตรวจสอบข้อมูลก่อนส่งกลับ

@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int):
    """
    Endpoint สำหรับดึงข้อมูลสินค้าตาม ID (จำลอง)
    """
    for item in fake_db:
        if item["id"] == item_id:
            return item
    raise HTTPException(status_code=404, detail="Item not found")

@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: int, item: Item):
    """
    Endpoint สำหรับอัปเดตข้อมูลสินค้าที่มีอยู่ทั้งหมด
    - รับ item_id เป็น Path Parameter
    - รับ Item object ใหม่ใน Request Body
    """
    for i, stored_item in enumerate(fake_db):
        if stored_item["id"] == item_id:
            updated_item_dict = item.dict()
            updated_item_dict["id"] = item_id # ให้แน่ใจว่า ID ยังคงเดิม
            fake_db[i] = updated_item_dict
            return updated_item_dict
    raise HTTPException(status_code=404, detail="Item not found for update")

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    """
    Endpoint สำหรับลบสินค้าตาม ID
    """
    global fake_db
    initial_len = len(fake_db)
    fake_db = [item for item in fake_db if item["id"] != item_id]
    if len(fake_db) == initial_len:
        raise HTTPException(status_code=404, detail="Item not found for deletion")
    return {"message": "Item deleted successfully"} # FastAPI จะส่ง 204 No Content กลับไปเพราะเรากำหนด status_code ไว้

อธิบายโค้ด:

  • class Item(BaseModel)::
    • เราสร้างคลาส Item ที่สืบทอดมาจาก pydantic.BaseModel ครับ
    • ภายในคลาสนี้ เรากำหนด Field ของข้อมูลที่เราคาดหวัง เช่น name: str, price: float
    • Type Hints (str, float, Optional[str]) จะถูกใช้โดย Pydantic เพื่อตรวจสอบชนิดข้อมูลครับ
    • Field(..., example="Laptop Pro"): ใช้ Field จาก Pydantic เพื่อเพิ่มคุณสมบัติพิเศษให้กับ Field เช่น:
      • ... (Ellipsis) หมายถึง Field นี้เป็น Required ครับ
      • example: ใช้สำหรับแสดงตัวอย่างในเอกสาร Swagger UI
      • gt=0: กำหนดเงื่อนไขว่า price ต้องมากกว่า 0 (Greater Than) Pydantic มี Validator อีกมากมาย เช่น ge (greater than or equal), lt (less than), le (less than or equal), min_length, max_length, regex เป็นต้นครับ
    • class Config: schema_extra: เป็นวิธีที่ใช้เพิ่มตัวอย่าง Request Body สำหรับแสดงในเอกสาร Swagger UI ครับ
  • @app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED):
    • create_item(item: Item): เมื่อเราประกาศพารามิเตอร์ของฟังก์ชันเป็น Pydantic Model (item: Item) FastAPI จะเข้าใจโดยอัตโนมัติว่าข้อมูลที่เข้ามาใน Request Body ควรจะตรงกับโครงสร้างของ Item Model ครับ
    • FastAPI จะทำการ:
      1. อ่าน JSON จาก Request Body
      2. แปลง JSON เป็น Python Dictionary
      3. ส่ง Dictionary นั้นไปให้ Pydantic เพื่อสร้าง Item object
      4. ตรวจสอบข้อมูล (Validation) ตามที่กำหนดใน Item Model (เช่น name ต้องเป็น str, price ต้องเป็น float และ > 0) หากมีข้อผิดพลาด Pydantic จะส่ง Error กลับมาทันที (FastAPI จะแปลงเป็น HTTP 422 Unprocessable Entity)
      5. ส่ง Item object ที่ผ่านการตรวจสอบแล้วเข้าสู่ฟังก์ชัน create_item
    • response_model=Item: กำหนดว่า Response ที่ส่งกลับไปควรจะเป็นไปตามโครงสร้างของ Item Model ครับ FastAPI จะทำการ Serialization (แปลง Python object เป็น JSON) และ Validation ข้อมูลขาออกให้ด้วย ทำให้มั่นใจว่า Client จะได้รับข้อมูลในรูปแบบที่ถูกต้องครับ
    • status_code=status.HTTP_201_CREATED: กำหนด HTTP Status Code ที่จะส่งกลับเมื่อการสร้างสำเร็จ ตามมาตรฐาน REST การสร้างทรัพยากรใหม่ควรส่ง 201 Created ครับ เราใช้ status.HTTP_201_CREATED เพื่อความชัดเจนและป้องกันการพิมพ์ผิดครับ
  • @app.put("/items/{item_id}", response_model=Item): คล้ายกับ POST แต่ใช้สำหรับอัปเดตข้อมูลทั้งหมดของทรัพยากรครับ
  • @app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT): สำหรับการลบข้อมูล เมื่อลบสำเร็จและไม่มีข้อมูลจะส่งกลับ ควรส่ง 204 No Content ครับ

การใช้ Pydantic Model ร่วมกับ FastAPI สำหรับ Request Body ทำให้การจัดการข้อมูลเป็นเรื่องง่าย, ปลอดภัย และมีประสิทธิภาพสูงครับ ไม่ต้องเขียนโค้ดตรวจสอบข้อมูลด้วยตัวเองเลย!

Data Validation และ Serialization ด้วย Pydantic

เราได้เห็น Pydantic ทำงานกับการตรวจสอบข้อมูล (Validation) สำหรับ Request Body ไปแล้วนะครับ แต่ Pydantic มีความสามารถมากกว่านั้นมาก และเป็นส่วนสำคัญที่ทำให้ FastAPI มีความโดดเด่นครับ

Pydantic เป็นไลบรารี Python ที่ช่วยให้เราสามารถกำหนด Schema ของข้อมูลโดยใช้ Python Type Hints และทำการตรวจสอบข้อมูล (Validation) และแปลงชนิดข้อมูล (Parsing) ให้โดยอัตโนมัติครับ

ความสามารถหลักของ Pydantic:

  1. Data Validation: ตรวจสอบว่าข้อมูลที่เข้ามา (เช่น จาก JSON, Dictionary) ตรงตาม Type Hints และเงื่อนไขที่เรากำหนดหรือไม่
  2. Data Parsing/Conversion: แปลงข้อมูลจากชนิดหนึ่งไปอีกชนิดหนึ่งโดยอัตโนมัติ (เช่น “123” เป็น 123)
  3. Data Serialization: แปลง Python Object (Pydantic Model) เป็น Python Dictionary หรือ JSON สำหรับส่งกลับไปยัง Client
  4. Schema Generation: สร้าง JSON Schema โดยอัตโนมัติ ซึ่ง FastAPI ใช้ในการสร้างเอกสาร OpenAPI (Swagger UI) ครับ

ตัวอย่างการใช้ Pydantic เพิ่มเติม

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr, HttpUrl
from typing import List, Optional, Set
from datetime import datetime, date, time, timedelta
from uuid import UUID

app = FastAPI()

# ... (โค้ดเก่า) ...

# Pydantic Model ที่ซับซ้อนขึ้น
class UserProfile(BaseModel):
    user_id: UUID = Field(..., description="Unique identifier for the user")
    username: str = Field(..., min_length=3, max_length=50, regex=r"^[a-zA-Z0-9_]+$")
    email: EmailStr = Field(..., example="[email protected]") # Pydantic มี EmailStr type
    is_active: bool = True
    registration_date: date = Field(default_factory=date.today) # กำหนดค่าเริ่มต้นเป็นวันที่ปัจจุบัน
    last_login: Optional[datetime] = None
    website: Optional[HttpUrl] = None # Pydantic มี HttpUrl type

class ProductDetails(BaseModel):
    product_name: str
    description: Optional[str] = None
    price: float = Field(..., gt=0)
    tags: Set[str] = Field(default_factory=set, description="Set of tags for the product") # ใช้ Set เพื่อให้ unique

class Order(BaseModel):
    order_id: int
    products: List[ProductDetails] # สามารถมี Pydantic Model ซ้อนกันได้
    order_date: datetime = Field(default_factory=datetime.now)
    customer_info: UserProfile # อีกหนึ่งตัวอย่างการซ้อน Model
    total_amount: float

@app.post("/users/", response_model=UserProfile)
async def create_user(user: UserProfile):
    """
    สร้าง User Profile ใหม่ พร้อม Data Validation ที่เข้มงวด
    """
    return user

@app.post("/orders/", response_model=Order)
async def create_order(order: Order):
    """
    สร้าง Order ใหม่ พร้อม Data Validation ที่ซับซ้อน
    """
    # ในโลกจริง อาจจะบันทึกลงฐานข้อมูลและส่งคืน order ที่มี ID จริง
    return order

อธิบาย Pydantic Features เพิ่มเติม:

  • Built-in Types: Pydantic รองรับ Type Hints มาตรฐานของ Python ทั้งหมด (str, int, float, bool, List, Dict, Set, Optional, Union) และมี Type พิเศษของตัวเอง เช่น
    • EmailStr: ตรวจสอบว่าเป็นรูปแบบอีเมลที่ถูกต้องหรือไม่
    • HttpUrl: ตรวจสอบว่าเป็น URL ที่ถูกต้องหรือไม่
    • UUID: ตรวจสอบว่าเป็น UUID ที่ถูกต้องหรือไม่
    • datetime, date, time, timedelta: รองรับการตรวจสอบและแปลงชนิดข้อมูลวันที่และเวลา
  • Field(...): ใช้ Field เพื่อกำหนดเงื่อนไขเพิ่มเติม:
    • min_length, max_length: สำหรับ String
    • gt, lt, ge, le: สำหรับตัวเลข
    • regex: สำหรับการตรวจสอบรูปแบบด้วย Regular Expression
    • description, example: สำหรับข้อมูลในเอกสาร API
    • default_factory: ใช้สำหรับกำหนดค่าเริ่มต้นที่มาจากการเรียกใช้ฟังก์ชัน (เช่น datetime.now หรือ date.today)
  • Nested Models: Pydantic ช่วยให้เราสามารถสร้าง Model ที่ซับซ้อนโดยการซ้อน Model อื่น ๆ เข้าไปได้ (เช่น products: List[ProductDetails] และ customer_info: UserProfile ใน Order Model) ซึ่งช่วยให้การออกแบบ Schema มีความยืดหยุ่นและเป็นระเบียบครับ
  • Set[str]: การใช้ Set จะบังคับให้ค่าใน List นั้นไม่ซ้ำกันครับ

ด้วย Pydantic, การจัดการข้อมูลที่ซับซ้อนใน API กลายเป็นเรื่องง่ายอย่างไม่น่าเชื่อ ไม่เพียงแต่ลดจำนวนโค้ดที่ต้องเขียนเพื่อตรวจสอบข้อมูล แต่ยังช่วยให้ API ของคุณมีความเสถียรและเชื่อถือได้มากขึ้นด้วยครับ

การจัดการ Error และ Exception

การจัดการข้อผิดพลาดเป็นสิ่งสำคัญในการสร้าง API ที่แข็งแกร่งและเป็นมิตรกับผู้ใช้งานครับ เมื่อเกิดข้อผิดพลาดขึ้นใน Server เราควรส่ง Response ที่มี Status Code และข้อความที่ชัดเจนกลับไปยัง Client เพื่อให้ Client สามารถเข้าใจและจัดการกับข้อผิดพลาดนั้นได้อย่างเหมาะสมครับ

FastAPI มีกลไกในการจัดการ Error และ Exception ที่ใช้งานง่ายและมีประสิทธิภาพครับ

1. การใช้ HTTPException

นี่คือวิธีมาตรฐานในการส่ง HTTP Error Response ใน FastAPI ครับ

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI, HTTPException, status

app = FastAPI()

# ... (โค้ดเก่า) ...

# ข้อมูลผู้ใช้งานจำลอง
fake_users_db = {
    "john": {"full_name": "John Doe", "email": "[email protected]"},
    "jane": {"full_name": "Jane Doe", "email": "[email protected]"},
}

@app.get("/users/{username}")
async def read_user(username: str):
    """
    Endpoint สำหรับดึงข้อมูลผู้ใช้งานตาม username
    จะส่ง HTTP 404 หากไม่พบผู้ใช้งาน
    """
    if username not in fake_users_db:
        # ยกเว้น HTTPException พร้อม status_code และ detail message
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, # ใช้ status object เพื่อความชัดเจน
            detail="User not found",
            headers={"X-Error": "There goes my error"}, # สามารถเพิ่ม headers ได้
        )
    return fake_users_db[username]

@app.post("/items_protected/")
async def create_protected_item(item_name: str, token: str):
    """
    ตัวอย่าง Endpoint ที่ต้องการ token แต่ไม่ได้ตรวจสอบจริงจัง
    จะส่ง HTTP 401 หาก token ไม่ถูกต้อง
    """
    if token != "secret-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return {"message": f"Item '{item_name}' created successfully with token."}

อธิบาย:

  • เมื่อคุณยกเว้น HTTPException FastAPI จะจัดการแปลงมันให้เป็น HTTP Response ที่มี Status Code ที่ระบุและ JSON Body ที่มี "detail" message ครับ
  • เราใช้ status โมดูลจาก fastapi เพื่อเข้าถึง HTTP Status Codes ที่เป็นมาตรฐาน เช่น status.HTTP_404_NOT_FOUND ซึ่งช่วยให้โค้ดอ่านง่ายและลดโอกาสเกิดข้อผิดพลาดครับ
  • คุณสามารถเพิ่ม headers เข้าไปใน HTTPException ได้ด้วย ซึ่งมีประโยชน์สำหรับ Error บางประเภท เช่น WWW-Authenticate สำหรับ 401 Unauthorized ครับ

2. การจัดการ Custom Exception (Custom Exception Handlers)

บางครั้งคุณอาจมี Custom Exception ที่คุณต้องการจัดการในลักษณะเฉพาะ หรือต้องการเปลี่ยนรูปแบบ Error Response สำหรับ Exception บางประเภทครับ

# main.py (เพิ่มโค้ดในไฟล์เดิม)
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.responses import JSONResponse

app = FastAPI()

# ... (โค้ดเก่า) ...

# 1. สร้าง Custom Exception ของเราเอง
class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name

# 2. สร้าง Custom Exception Handler
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    """
    Custom exception handler สำหรับ UnicornException
    จะส่งคืน JSON response ที่กำหนดเอง
    """
    return JSONResponse(
        status_code=status.HTTP_418_IM_A_TEAPOT, # ตัวอย่าง status code ที่ไม่ธรรมดา
        content={"message": f"Oops! {exc.name} did something wrong. But I'm a teapot!"},
    )

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    """
    Endpoint ที่จะยกเว้น UnicornException
    """
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

อธิบาย:

  • class UnicornException(Exception):: เราสร้างคลาส Custom Exception ที่สืบทอดมาจาก Exception ครับ
  • @app.exception_handler(UnicornException): นี่คือ Decorator ที่ใช้ลงทะเบียนฟังก์ชัน unicorn_exception_handler ให้เป็นตัวจัดการสำหรับ UnicornException ครับ
  • async def unicorn_exception_handler(request: Request, exc: UnicornException)::
    • ฟังก์ชันนี้จะรับ Request object และ UnicornException ที่ถูกยกเว้นเข้ามาครับ
    • เราสามารถสร้าง JSONResponse ที่กำหนดเองได้ โดยระบุ status_code และ content (ซึ่งควรเป็น Dictionary ที่จะถูกแปลงเป็น JSON)

การจัดการ Error อย่างมีประสิทธิภาพไม่เพียงแต่ทำให้ API ของคุณน่าเชื่อถือ แต่ยังช่วยให้ Client สามารถพัฒนาส่วนที่จัดการข้อผิดพลาดได้ง่ายขึ้นด้วยครับ

การเชื่อมต่อกับฐานข้อมูล (SQLAlchemy ORM)

API ส่วนใหญ่จำเป็นต้องมีการจัดเก็บและดึงข้อมูลจากฐานข้อมูลครับ ในโลกของ Python, SQLAlchemy เป็น ORM (Object-Relational Mapper) ที่ได้รับความนิยมและทรงพลังที่สุดตัวหนึ่ง และสามารถทำงานร่วมกับ FastAPI ได้อย่างยอดเยี่ยมครับ

เราจะใช้ SQLAlchemy เพื่อเชื่อมต่อกับฐานข้อมูล SQLite ซึ่งเป็นฐานข้อมูลแบบไฟล์ที่ใช้งานง่าย เหมาะสำหรับการเริ่มต้นและพัฒนาครับ

1. ติดตั้ง Dependencies

ติดตั้ง SQLAlchemy และ sqlite3 (ซึ่งมักจะมาพร้อม Python อยู่แล้ว แต่ถ้าใช้ฐานข้อมูลอื่น เช่น PostgreSQL, MySQL ก็ต้องติดตั้ง Driver เพิ่มเติมครับ)

pip install sqlalchemy

2. กำหนด Database Configuration และ Models

สร้างไฟล์ database.py และ models.py เพื่อแยกโค้ดเกี่ยวกับฐานข้อมูลออกจาก main.py ครับ

database.py:

# database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# URL ของฐานข้อมูล SQLite
# "sqlite:///./sql_app.db" หมายถึงไฟล์ฐานข้อมูลชื่อ sql_app.db จะถูกสร้างในโฟลเดอร์เดียวกันกับ project
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"

# สร้าง SQLAlchemy Engine
# connect_args={"check_same_thread": False} เป็นสิ่งจำเป็นสำหรับ SQLite
# เพราะ SQLite ไม่ได้ถูกออกแบบมาให้ทำงานพร้อมกันหลายเธรด
# แต่ FastAPI อาจจะใช้หลายเธรดในการจัดการ request
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

# สร้าง SessionLocal class
# แต่ละ instance ของ SessionLocal จะเป็น session ของฐานข้อมูล
# SessionLocal จะถูกใช้เมื่อมีการเรียก request เข้ามา
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# สร้าง Base class สำหรับ declarative models
# ทุก models ของ SQLAlchemy จะต้องสืบทอดมาจาก Base
Base = declarative_base()

models.py:

# models.py
from sqlalchemy import Column, Integer, String, Float, Boolean, ForeignKey
from sqlalchemy.orm import relationship

from .database import Base # นำเข้า Base จาก database.py

# กำหนด SQLAlchemy Model สำหรับ Item
class Item(Base):
    __tablename__ = "items" # ชื่อตารางในฐานข้อมูล

    id = Column(Integer, primary_key=True, index=True) # Primary Key, มี index เพื่อการค้นหาเร็วขึ้น
    name = Column(String, index=True)
    description = Column(String, nullable=True) # nullable=True หมายถึง field นี้สามารถเป็น NULL ได้
    price = Column(Float)
    tax = Column(Float, nullable=True)
    is_available = Column(Boolean, default=True) # ค่าเริ่มต้นเป็น True

    # สามารถเพิ่มความสัมพันธ์กับตารางอื่นได้ในภายหลัง
    # owner_id = Column(Integer, ForeignKey("users.id"))
    # owner = relationship("User", back_populates="items")

3. สร้าง Pydantic Models สำหรับ Request/Response

เป็นแนวทางปฏิบัติที่ดีในการแยก Pydantic Models (ที่ใช้สำหรับ API Request/Response) ออกจาก SQLAlchemy Models (ที่ใช้สำหรับฐานข้อมูล) ครับ เพื่อป้องกันการเปิดเผยข้อมูลที่ไม่จำเป็นและเพิ่มความยืดหยุ่น

สร้างไฟล์ schemas.py:

# schemas.py
from pydantic import BaseModel, Field
from typing import Optional

# Pydantic Model สำหรับ Request Body (ใช้ในการสร้าง/อัปเดต Item)
class ItemBase(BaseModel):
    name: str = Field(..., min_length=3, max_length=100)
    description: Optional[str] = Field(None, max_length=500)
    price: float = Field(..., gt=0)
    tax: Optional[float] = None
    is_available: bool = True

    class Config:
        schema_extra = {
            "example": {
                "name": "Wireless Headphone",
                "description": "High-quality wireless headphones with noise cancellation.",
                "price": 199.99,
                "tax": 0.07,
                "is_available": True
            }
        }

# Pydantic Model สำหรับ Response Body (รวมถึง ID ที่สร้างโดย DB)
class ItemCreate(ItemBase):
    pass # ItemCreate เหมือนกับ ItemBase แต่แยกไว้เพื่อความชัดเจน

class ItemUpdate(ItemBase):
    # สำหรับการอัปเดต อาจจะให้ทุก Field เป็น Optional ก็ได้
    name: Optional[str] = Field(None, min_length=3, max_length=100)
    description: Optional[str] = Field(None, max_length=500)
    price: Optional[float] = Field(None, gt=0)
    tax: Optional[float] = None
    is_available: Optional[bool] = None

class Item(ItemBase):
    id: int # เพิ่ม ID ที่ได้จากฐานข้อมูล

    class Config:
        orm_mode = True # สำคัญมาก: บอก Pydantic ให้ทำงานกับ ORM objects (SQLAlchemy) ได้
        # หรือใน Pydantic v2+ ใช้ from_attributes = True

4. สร้างตารางในฐานข้อมูล

ในไฟล์ main.py หรือไฟล์เริ่มต้นอื่น ๆ ให้เรียกใช้ Base.metadata.create_all(bind=engine) เพื่อสร้างตารางตาม Models ที่เรากำหนดไว้ครับ

main.py (ไฟล์หลัก):

# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List

from . import models, schemas # นำเข้า models และ schemas ที่เราสร้าง
from .database import SessionLocal, engine # นำเข้า SessionLocal และ engine

# สร้างตารางในฐานข้อมูลหากยังไม่มี
# ควรเรียกเพียงครั้งเดียวเมื่อแอปพลิเคชันเริ่มต้น
models.Base.metadata.create_all(bind=engine)

app = FastAPI()

# Dependency สำหรับการรับ Database Session
# แต่ละ request จะได้ session ของตัวเอง และจะถูกปิดเมื่อ request เสร็จสิ้น
def get_db():
    db = SessionLocal()
    try:
        yield db # ส่ง db session ไปให้ Path Operation
    finally:
        db.close() # ปิด db session เมื่อทำงานเสร็จ

5. สร้าง CRUD Endpoints (Create, Read, Update, Delete)

กลับไปที่ main.py และเพิ่ม Path Operations สำหรับการทำงานกับ Item ในฐานข้อมูลครับ

# main.py (เพิ่มโค้ดในไฟล์เดิม)
# ... (โค้ดด้านบน) ...

# Endpoint สำหรับสร้าง Item ใหม่
@app.post("/items/", response_model=schemas.Item, status_code=status.HTTP_201_CREATED)
async def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
"""
สร้างรายการสินค้าใหม่ในฐานข้อมูล
"""
db_item = models.Item(name=item.name, description=item.description, price=item.price, tax=item.tax, is_available=item.is_available)
db.add(db_item) # เพิ่ม object เข้าสู่ session
db.commit() # บันทึกการเปลี่ยนแปลงลงฐานข้อมูล
db.refresh(db_item) # โหลดข้อมูลล่าสุดจาก DB กลับมายัง object (เช่น ID ที่ถูกสร้างขึ้น)
return db_item

# Endpoint สำหรับดึง Item ทั้งหมด
@app.get("/items/", response_model=List[schemas.Item])
async def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
"""
ดึงรายการสินค้าทั้งหมดจากฐานข้อมูล (พร้อม pagination)
"""
items = db.query(models.Item).offset(skip).limit(limit).all()
return items

# Endpoint สำหรับดึง Item ตาม ID
@app.get("/items/{item_id}", response_model=schemas.Item)
async def read_item(item_id: int, db: Session = Depends(get_db)):
"""
ดึงข้อมูลสินค้าเฉพาะตาม ID
"""
item = db.query(models.Item).filter(models.Item.id == item_id).first()
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item

# Endpoint สำหรับอัปเดต Item
@app.put("/items/{item_id}", response_model=schemas.Item)
async def update_item(item_id: int, item: schemas.ItemUpdate, db: Session = Depends(get_db)):
"""
อัปเดตข้อมูลสินค้าที่มีอยู่ทั้งหมด
"""
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")

# อัปเดตข้อมูลจาก item ที่ส่งมา
for key, value in item.dict(exclude_unset=True).items(): # exclude_unset=True จะอัปเดตเฉพาะ field ที่ถูกส่งมา
setattr(db_item, key, value)

db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item

# Endpoint สำหรับลบ Item
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int, db: Session = Depends(get_db)):
"""
ลบสินค้าออกจากฐานข้อมูล
"""
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")

db.delete(db_item)
db.commit()
# ไม่ต้อง return อะไรสำหรับ

จัดส่งรวดเร็วส่งด่วนทั่วประเทศ
รับประกันสินค้าเคลมง่าย มีใบรับประกัน
ผ่อนชำระได้บัตรเครดิต 0% สูงสุด 10 เดือน
สะสมแต้ม รับส่วนลดส่วนลดและคะแนนสะสม

© 2026 SiamLancard — จำหน่ายการ์ดแลน อุปกรณ์ Server และเครื่องพิมพ์ใบเสร็จ

SiamLancard
Logo
Free Forex EA Download — XM Signal · EA Forex ฟรี
iCafeForex.com - สอนเทรด Forex | SiamCafe.net
Shopping cart