
สวัสดีครับ! ในโลกของการพัฒนาซอฟต์แวร์ยุคใหม่ การสร้าง API (Application Programming Interface) ถือเป็นหัวใจสำคัญที่เชื่อมโยงระบบต่าง ๆ เข้าไว้ด้วยกัน และ FastAPI คือหนึ่งในสุดยอดเฟรมเวิร์ค Python ที่เข้ามาปฏิวัติวงการด้วยความเร็ว ประสิทธิภาพ และความง่ายในการใช้งาน ที่สำคัญคือมันถูกสร้างขึ้นมาเพื่อการพัฒนา REST API โดยเฉพาะเลยครับ บทความนี้จะพาทุกท่านดำดิ่งสู่โลกของ FastAPI ตั้งแต่เริ่มต้นติดตั้ง ไปจนถึงการสร้าง REST API ที่ครบวงจร พร้อมฟีเจอร์เด็ด ๆ อย่างการเชื่อมต่อฐานข้อมูล, การจัดการข้อมูลด้วย Pydantic, ระบบยืนยันตัวตน, และการนำไปใช้งานจริง เพื่อให้คุณพร้อมที่จะสร้างสรรค์ API คุณภาพสูงได้อย่างมืออาชีพครับ
สารบัญ
- ทำไมต้อง FastAPI? (What is FastAPI and Why Use It?)
- การเตรียมความพร้อมก่อนเริ่มต้น
- การติดตั้ง FastAPI และ Uvicorn
- สร้าง REST API แรกของเรา: Hello World
- ทำความเข้าใจโครงสร้างหลักของ FastAPI
- การจัดการพารามิเตอร์ (Parameters) ใน API
- เจาะลึก Pydantic: การตรวจสอบและจัดการข้อมูล
- การเชื่อมต่อฐานข้อมูล: FastAPI กับ SQLAlchemy และ SQLite
- การจัดการข้อผิดพลาด (Error Handling)
- การตรวจสอบสิทธิ์ (Authentication) และการอนุญาต (Authorization)
- การจัดการไฟล์ (File Uploads)
- การทำ Asynchronous Operations (Async/Await)
- การทดสอบ (Testing) API ของคุณ
- การนำไปใช้งานจริง (Deployment)
- ตารางเปรียบเทียบ: FastAPI vs. Flask vs. Django REST Framework
- คำถามที่พบบ่อย (FAQ)
- สรุปและ Call-to-Action
ทำไมต้อง FastAPI? (What is FastAPI and Why Use It?)
FastAPI คือเว็บเฟรมเวิร์คสำหรับสร้าง API ที่ทันสมัย รวดเร็ว และมีประสิทธิภาพสูง ซึ่งพัฒนาขึ้นบนพื้นฐานของ Python 3.7+ และมาตรฐาน Type Hints ความน่าสนใจของ FastAPI อยู่ที่การผสมผสานจุดเด่นของเฟรมเวิร์คอื่น ๆ เข้าไว้ด้วยกัน พร้อมทั้งเพิ่มฟีเจอร์ที่ช่วยให้นักพัฒนาทำงานได้ง่ายและเร็วขึ้นมากครับ
จุดเด่นของ FastAPI:
- ความเร็วสูง (High Performance): สร้างขึ้นบน Starlette (สำหรับเว็บ) และ Pydantic (สำหรับการจัดการข้อมูล) ทำให้มีประสิทธิภาพเทียบเท่ากับ Node.js และ Go ซึ่งเป็นภาษาที่ขึ้นชื่อเรื่องความเร็วในการประมวลผล I/O Bound Operations ครับ
- เขียนโค้ดได้รวดเร็ว (Fast to Code): ลดเวลาในการพัฒนาได้ถึง 20-40% เพราะมีการใช้ Type Hints ของ Python และ Pydantic ในการตรวจสอบข้อมูลอัตโนมัติ ทำให้โค้ดกระชับขึ้นและลดโอกาสเกิดข้อผิดพลาด
- ลด Bug (Fewer Bugs): ระบบ Type Hints และ Pydantic ช่วยให้คุณจับข้อผิดพลาดที่เกี่ยวกับ Type ได้ตั้งแต่ตอนเขียนโค้ด ทำให้ Bug ลดลงถึง 50% ครับ
- ใช้งานง่าย (Easy to Use): มีการเรียนรู้ที่ค่อนข้างต่ำ (low learning curve) สำหรับผู้ที่คุ้นเคยกับ Python อยู่แล้ว
- เอกสาร API อัตโนมัติ (Automatic Interactive API Documentation): FastAPI สร้างเอกสาร API แบบอินเทอร์แอคทีฟให้โดยอัตโนมัติด้วย Swagger UI (OpenAPI) และ ReDoc ซึ่งช่วยให้การทดสอบและทำความเข้าใจ API ทำได้ง่ายขึ้นมาก ทั้งสำหรับนักพัฒนา API เองและผู้ใช้ API ครับ
- มาตรฐาน (Standard-based): สนับสนุนมาตรฐาน OpenAPI (เดิมคือ Swagger) สำหรับการสร้าง API และ JSON Schema สำหรับการตรวจสอบข้อมูล ซึ่งเป็นมาตรฐานที่ได้รับการยอมรับในอุตสาหกรรม
- Dependency Injection ที่ทรงพลัง: ช่วยให้การจัดการ Dependencies ทำได้ง่ายและยืดหยุ่น ทำให้โค้ดเป็นระเบียบและทดสอบได้ง่ายขึ้นครับ
ด้วยเหตุผลเหล่านี้ FastAPI จึงเป็นตัวเลือกที่ยอดเยี่ยมสำหรับการสร้าง REST API ที่ต้องการประสิทธิภาพ ความเร็วในการพัฒนา และความน่าเชื่อถือครับ
การเตรียมความพร้อมก่อนเริ่มต้น
ก่อนที่เราจะเริ่มลงมือสร้าง API กันจริงจัง เรามาเตรียมเครื่องมือและทำความเข้าใจพื้นฐานบางอย่างกันก่อนครับ
ติดตั้ง Python และ Virtual Environment
สิ่งแรกที่คุณต้องมีคือ Python ครับ แนะนำให้ใช้ Python 3.7 ขึ้นไป เพื่อให้เข้ากันได้กับ FastAPI
- ติดตั้ง Python:
- สำหรับ Windows: ดาวน์โหลด Installer จาก python.org และอย่าลืมติ๊กช่อง “Add Python to PATH” ระหว่างติดตั้งครับ
- สำหรับ macOS: ส่วนใหญ่มี Python ติดตั้งมาให้อยู่แล้ว แต่อาจเป็นเวอร์ชันเก่า แนะนำให้ติดตั้งผ่าน Homebrew (`brew install python3`)
- สำหรับ Linux: ติดตั้งผ่าน Package Manager ของคุณ (`sudo apt install python3` สำหรับ Debian/Ubuntu หรือ `sudo yum install python3` สำหรับ Fedora/CentOS)
- สร้าง Virtual Environment: การใช้ Virtual Environment เป็นสิ่งสำคัญมากในการแยกโปรเจกต์ Python ออกจากกัน เพื่อป้องกันความขัดแย้งของ Dependency ครับ
# สร้าง virtual environment ชื่อ .venv
python3 -m venv .venv
# เปิดใช้งาน virtual environment
# สำหรับ macOS/Linux
source .venv/bin/activate
# สำหรับ Windows
.venv\Scripts\activate
เมื่อเปิดใช้งานแล้ว คุณจะเห็นชื่อของ environment (เช่น `(.venv)`) นำหน้า prompt ใน Terminal ของคุณครับ
ตั้งค่า IDE และเครื่องมือที่จำเป็น
เพื่อประสบการณ์การพัฒนาที่ดีเยี่ยม ผมขอแนะนำ Visual Studio Code (VS Code) ครับ เป็น IDE ที่ฟรี ทรงพลัง และมี Extension มากมาย
- ติดตั้ง VS Code: ดาวน์โหลดและติดตั้งจากเว็บไซต์ทางการ
- Extension ที่แนะนำ:
- Python: จาก Microsoft สำหรับการรองรับ Python อย่างสมบูรณ์ (IntelliSense, Debugging, Formatting)
- Pylance: สำหรับการตรวจสอบ Type และการแนะนำโค้ดที่ฉลาดขึ้น
- Black Formatter: สำหรับจัดรูปแบบโค้ด Python ให้สวยงามตามมาตรฐาน PEP 8
- isort: สำหรับจัดเรียง Import statements
พื้นฐาน REST API ที่ควรรู้
ก่อนที่เราจะเริ่มสร้าง API เรามาทบทวนแนวคิดพื้นฐานของ REST API กันเล็กน้อยครับ
- REST (Representational State Transfer): เป็นสถาปัตยกรรมสำหรับออกแบบ API ที่เน้นการสื่อสารแบบ Stateless ระหว่าง Client กับ Server โดยใช้ทรัพยากร (Resources) เป็นศูนย์กลางครับ
- Resources: ทุกอย่างที่เราต้องการจัดการจะถูกมองว่าเป็น Resource ตัวอย่างเช่น `/users`, `/products`, `/orders`
- HTTP Methods (Verbs): ใช้เพื่อระบุการกระทำที่เราต้องการทำกับ Resource นั้น ๆ ครับ
- GET: ดึงข้อมูลจาก Server (อ่าน)
- POST: ส่งข้อมูลใหม่ไปยัง Server (สร้าง)
- PUT: อัปเดตข้อมูลที่มีอยู่แล้วทั้งหมดบน Server (แก้ไขทั้งหมด)
- PATCH: อัปเดตข้อมูลที่มีอยู่แล้วบางส่วนบน Server (แก้ไขบางส่วน)
- DELETE: ลบข้อมูลออกจาก Server (ลบ)
- Status Codes: ตัวเลข 3 หลักที่ Server ส่งกลับมาเพื่อบอกสถานะของการร้องขอ (เช่น 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error)
- JSON (JavaScript Object Notation): รูปแบบข้อมูลที่นิยมใช้ในการสื่อสารระหว่าง Client และ Server เนื่องจากอ่านง่ายและเข้ากันได้กับหลายภาษาโปรแกรมครับ
การติดตั้ง FastAPI และ Uvicorn
เมื่อเตรียมพร้อมแล้ว เรามาติดตั้ง FastAPI และ Uvicorn กันครับ
- FastAPI: ตัวเฟรมเวิร์คหลัก
- Uvicorn: ASGI Server (Asynchronous Server Gateway Interface) ที่ใช้รันแอปพลิเคชัน FastAPI ในการพัฒนาและใน Production ครับ
ใน Terminal ที่เปิดใช้งาน Virtual Environment อยู่ ให้รันคำสั่งนี้ครับ:
pip install fastapi "uvicorn[standard]"
"uvicorn[standard]" จะติดตั้ง Uvicorn พร้อม Dependencies เพิ่มเติมที่จำเป็น เช่น `h11` และ `websockets` เพื่อการทำงานที่สมบูรณ์แบบครับ
สร้าง REST API แรกของเรา: Hello World
มาเริ่มต้นสร้าง API ง่าย ๆ กันครับ สร้างไฟล์ชื่อ `main.py` ในโปรเจกต์ของคุณ:
# main.py
from fastapi import FastAPI
# สร้าง instance ของ FastAPI
app = FastAPI()
# กำหนด Path Operation สำหรับ HTTP GET request ที่ root URL ("/")
@app.get("/")
async def read_root():
return {"message": "Hello, SiamLancard.com! Welcome to FastAPI."}
# กำหนด Path Operation สำหรับ HTTP GET request ที่ "/items/{item_id}"
# โดยมี Path Parameter คือ item_id และ Query Parameter คือ q
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
รัน API และทดสอบ
เปิด Terminal ในโฟลเดอร์โปรเจกต์ของคุณ (ที่เปิดใช้งาน Virtual Environment แล้ว) และรันคำสั่ง Uvicorn ครับ:
uvicorn main:app --reload
main:app: บอก Uvicorn ว่าให้หา object `app` ภายในไฟล์ `main.py`--reload: โหมด Development ที่จะทำให้ Server รีโหลดอัตโนมัติเมื่อคุณแก้ไขไฟล์โค้ด (สะดวกมาก ๆ ครับ)
เมื่อรันสำเร็จ คุณจะเห็นข้อความประมาณนี้:
INFO: Will watch for changes in these directories: ['/path/to/your/project']
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.
ตอนนี้คุณสามารถเปิดเว็บเบราว์เซอร์ไปที่ http://127.0.0.1:8000 เพื่อดูผลลัพธ์ของ `read_root()` ซึ่งจะแสดง JSON ว่า {"message": "Hello, SiamLancard.com! Welcome to FastAPI."} ครับ
ลองเรียกอีก Endpoint: http://127.0.0.1:8000/items/5?q=somequery คุณจะเห็น {"item_id": 5, "q": "somequery"}
สำรวจ Automatic Interactive API Documentation (Swagger UI & ReDoc)
นี่คือหนึ่งในฟีเจอร์ที่โดดเด่นที่สุดของ FastAPI ครับ เมื่อ Server รันอยู่ คุณสามารถเข้าถึงเอกสาร API ได้โดยอัตโนมัติ:
- Swagger UI: http://127.0.0.1:8000/docs (เป็น UI ที่นิยมใช้ในการทดสอบ API)
- ReDoc: http://127.0.0.1:8000/redoc (เป็น UI ที่เน้นการอ่านและทำความเข้าใจเอกสาร)
คุณจะเห็น Endpoints ที่เราสร้างไว้ พร้อมรายละเอียดของ Path Parameters, Query Parameters และสามารถทดลองส่ง Request ได้จากหน้าเว็บนั้นเลยครับ สะดวกมาก ๆ
ทำความเข้าใจโครงสร้างหลักของ FastAPI
เมื่อเราได้สร้างและรัน API แรกไปแล้ว มาทำความเข้าใจโครงสร้างพื้นฐานกันให้ลึกซึ้งขึ้นครับ
Path Operations และ HTTP Methods
ใน FastAPI การสร้าง “Path Operation” คือการผูกฟังก์ชัน Python เข้ากับ Path (URL) และ HTTP Method (GET, POST, PUT, DELETE) ที่เฉพาะเจาะจงครับ
from fastapi import FastAPI
app = FastAPI()
# GET request: ดึงข้อมูล
@app.get("/users/")
async def get_users():
return [{"username": "Alice"}, {"username": "Bob"}]
# POST request: สร้างข้อมูลใหม่
@app.post("/users/")
async def create_user(user_data: dict):
return {"message": "User created", "data": user_data}
# PUT request: อัปเดตข้อมูลทั้งหมด
@app.put("/users/{user_id}")
async def update_user(user_id: int, user_data: dict):
return {"message": f"User {user_id} updated", "data": user_data}
# DELETE request: ลบข้อมูล
@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
return {"message": f"User {user_id} deleted"}
# PATCH request: อัปเดตข้อมูลบางส่วน (มักใช้ Pydantic ในการจัดการ)
@app.patch("/users/{user_id}")
async def patch_user(user_id: int, user_data: dict):
return {"message": f"User {user_id} partially updated", "data": user_data}
จะเห็นได้ว่าแต่ละ Decorator (`@app.get`, `@app.post`, ฯลฯ) จะรับ Path เป็นอาร์กิวเมนต์แรก และฟังก์ชันที่ตามมาคือ Path Operation Function ที่จะถูกเรียกใช้เมื่อมี Request เข้ามาตรงกับ Path และ Method นั้น ๆ ครับ
การจัดระเบียบ API ด้วย APIRouter
เมื่อแอปพลิเคชันของคุณใหญ่ขึ้น การเก็บ Path Operations ทั้งหมดไว้ในไฟล์ `main.py` เดียวจะไม่เป็นระเบียบครับ FastAPI มี `APIRouter` ที่ช่วยให้เราสามารถแยก Path Operations ออกเป็นโมดูลย่อย ๆ ได้
1. สร้างไฟล์ `routers/items.py` สำหรับจัดการ API ที่เกี่ยวกับ Item:
# routers/items.py
from fastapi import APIRouter
router = APIRouter(
prefix="/items", # กำหนด prefix สำหรับทุก Path ใน Router นี้
tags=["Items"], # กำหนด Tag สำหรับเอกสาร API
responses={404: {"description": "Not found"}},
)
fake_items_db = {"foo": {"name": "Foo", "price": 50.2}, "bar": {"name": "Bar", "price": 62.0}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
# สามารถเรียก HTTPException ได้แม้จะอยู่ใน Router
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Item not found")
return fake_items_db[item_id]
@router.post("/")
async def create_item(item: dict):
fake_items_db[item["name"]] = item
return {"message": "Item created successfully", "item": item}
2. แก้ไข `main.py` เพื่อรวม Router เข้ามา:
# main.py
from fastapi import FastAPI
from routers import items # import router ที่เราสร้าง
app = FastAPI(
title="SiamLancard FastAPI Demo", # กำหนดชื่อ API ในเอกสาร
description="A comprehensive guide to building REST APIs with FastAPI.",
version="1.0.0",
)
# รวม router เข้ามาในแอปพลิเคชันหลัก
app.include_router(items.router)
@app.get("/")
async def root():
return {"message": "Welcome to the FastAPI Demo API!"}
ตอนนี้ Endpoint ของ Items จะเข้าถึงได้ที่ `/items/` และ `/items/{item_id}` โดยอัตโนมัติ และเอกสาร API จะถูกจัดกลุ่มภายใต้ Tag “Items” ครับ
การจัดการพารามิเตอร์ (Parameters) ใน API
FastAPI ทำให้การจัดการพารามิเตอร์ต่าง ๆ เป็นเรื่องง่ายด้วย Python Type Hints ครับ
Path Parameters: ระบุข้อมูลใน URL
ใช้เพื่อระบุส่วนหนึ่งของ URL ที่เป็นตัวแปร มักใช้ในการระบุ Resource ที่ต้องการเข้าถึง
# main.py (หรือใน router)
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int): # FastAPI จะแปลง user_id เป็น int อัตโนมัติ
return {"user_id": user_id, "message": f"Fetching user with ID {user_id}"}
@app.get("/files/{file_path:path}") # ใช้ :path เพื่อให้ยอมรับ path ที่มี / ได้
async def get_file_path(file_path: str):
return {"file_path": file_path}
ลองไปที่ `/users/123` หรือ `/files/home/user/document.txt` ครับ
Query Parameters: กรองและจัดเรียงข้อมูล
ใช้ในการส่งข้อมูลเพิ่มเติมที่ไม่ใช่ส่วนหนึ่งของ Path มักใช้สำหรับการกรอง การจัดเรียง หรือ Pagination
# main.py (หรือใน router)
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
@app.get("/products/")
async def read_products(
limit: int = 10, # default value 10
offset: int = 0, # default value 0
search: Optional[str] = None # Optional parameter
):
results = {"limit": limit, "offset": offset}
if search:
results["search"] = search
# ในความเป็นจริง เราจะใช้ limit, offset, search ไป query จาก database
return results
@app.get("/items_with_validation/")
async def read_items_with_validation(
# ใช้ Query เพื่อเพิ่มการตรวจสอบและ metadata
q: Optional[str] = Query(
None,
min_length=3,
max_length=50,
title="Search query",
description="Search term for items in the database"
)
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
ลองเรียก: `/products/?limit=5&offset=10&search=keyboard` หรือ `/items_with_validation/?q=apple`
FastAPI จะใช้ Type Hints ในการตรวจสอบ (validation) และแปลงข้อมูลโดยอัตโนมัติ ถ้าข้อมูลที่ส่งมาไม่ตรง Type (เช่น ส่ง `limit=abc`) FastAPI จะส่ง HTTP 422 Unprocessable Entity กลับไปพร้อมรายละเอียดข้อผิดพลาดทันทีครับ
Request Body และ Pydantic Models: ส่งข้อมูลที่ซับซ้อน
สำหรับ HTTP Methods อย่าง POST, PUT, PATCH เรามักจะส่งข้อมูลที่ซับซ้อน (เช่น JSON object) ไปยัง Server ซึ่ง FastAPI ใช้ Pydantic ในการจัดการ Request Body ได้อย่างยอดเยี่ยมครับ
# main.py (หรือใน router)
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional
app = FastAPI()
# กำหนด Pydantic Model สำหรับ Item
class Item(BaseModel):
name: str = Field(..., example="Laptop Pro") # ... คือ required
description: Optional[str] = Field(None, example="Powerful laptop for professionals")
price: float = Field(..., gt=0, example=1200.0) # gt=0 คือต้องมากกว่า 0
tax: Optional[float] = Field(None, example=120.0)
# กำหนด Pydantic Model สำหรับ User
class User(BaseModel):
username: str
email: str
full_name: Optional[str] = None
disabled: Optional[bool] = None
@app.post("/items/")
async def create_item(item: Item): # FastAPI จะรับ Request Body มาสร้างเป็น object Item
item_dict = item.dict() # แปลง Pydantic Model เป็น Python dict
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
@app.put("/users/{user_id}")
async def update_user(user_id: int, user: User):
return {"user_id": user_id, **user.dict()}
เมื่อคุณเข้าดูเอกสาร Swagger UI (`/docs`) คุณจะเห็น Schema ของ `Item` และ `User` โดยอัตโนมัติ และสามารถทดลองส่ง JSON ที่ถูกต้องตาม Model ได้เลยครับ FastAPI จะตรวจสอบความถูกต้องของข้อมูล (Data Validation) ให้โดยอัตโนมัติ และแปลง JSON เป็น Python object ที่เป็น Type ของ Pydantic Model ให้เราใช้งานได้ทันที
เจาะลึก Pydantic: การตรวจสอบและจัดการข้อมูล
Pydantic คือไลบรารีที่ FastAPI ใช้ในการประกาศ Schema ของข้อมูล, ตรวจสอบความถูกต้อง, และแปลงข้อมูล (serialization/deserialization) ทำให้การจัดการข้อมูลเป็นเรื่องง่ายและปลอดภัยครับ
การสร้างและตรวจสอบข้อมูลด้วย BaseModel
ทุก Pydantic Model จะสืบทอดมาจาก `BaseModel` ครับ
# models.py (แยกไฟล์เพื่อความเป็นระเบียบ)
from pydantic import BaseModel
from typing import List, Optional
class Product(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float
tags: List[str] = [] # กำหนดค่า default เป็น list ว่าง
class Order(BaseModel):
order_id: str
products: List[Product]
customer_email: str
total_amount: float
status: str = "pending" # กำหนดค่า default
# ใน Path Operation
@app.post("/products/")
async def create_product(product: Product):
# product จะถูกตรวจสอบความถูกต้องตาม Product Model โดยอัตโนมัติ
return {"message": "Product created", "product": product.dict()}
ถ้า Request Body ไม่ตรงตาม Schema ของ `Product` (เช่น `price` ไม่ใช่ float, `id` ไม่มี) FastAPI จะตอบกลับด้วย HTTP 422 Unprocessable Entity ทันทีครับ
การใช้ Field สำหรับการตรวจสอบขั้นสูง
เราสามารถใช้ `Field` จาก Pydantic เพื่อเพิ่มเงื่อนไขการตรวจสอบที่ซับซ้อนขึ้นได้ครับ
from pydantic import BaseModel, Field
from typing import Optional
class UserRegistration(BaseModel):
username: str = Field(..., min_length=3, max_length=20, regex="^[a-zA-Z0-9_]+$")
email: str = Field(..., example="[email protected]") # Pydantic ตรวจสอบรูปแบบอีเมลอัตโนมัติ
password: str = Field(..., min_length=8, max_length=64)
age: Optional[int] = Field(None, gt=0, le=120) # ต้องมากกว่า 0 และน้อยกว่าหรือเท่ากับ 120
is_active: bool = True # ค่าเริ่มต้นเป็น True
ตัวอย่างการใช้งาน `Field`:
...: หมายถึง Required Fieldmin_length,max_length: สำหรับ Stringgt(greater than),ge(greater than or equal),lt(less than),le(less than or equal): สำหรับตัวเลขregex: สำหรับตรวจสอบรูปแบบ String ด้วย Regular Expressionexample: เพิ่มตัวอย่างข้อมูลในเอกสาร Swagger UI
Nested Models และการสร้างโครงสร้างข้อมูลที่ซับซ้อน
Pydantic อนุญาตให้คุณสร้าง Model ที่ซ้อนกันได้ เพื่อจัดการข้อมูลที่มีความสัมพันธ์กัน
from pydantic import BaseModel, HttpUrl
from typing import List, Optional
class Address(BaseModel):
street: str
city: str
zip_code: str
country: str
class Company(BaseModel):
name: str
location: Address
website: Optional[HttpUrl] = None # Pydantic ตรวจสอบ URL อัตโนมัติ
class Employee(BaseModel):
id: int
name: str
email: str
position: str
company: Company # Nested Model
skills: List[str] = []
@app.post("/employees/")
async def create_employee(employee: Employee):
return {"message": "Employee data received", "employee": employee.dict()}
เมื่อคุณส่ง JSON สำหรับ `/employees/` คุณจะต้องส่ง JSON ที่มีโครงสร้างตรงตาม `Employee` ซึ่งรวมถึง `Company` และ `Address` ที่ซ้อนอยู่ข้างในด้วยครับ
การเชื่อมต่อฐานข้อมูล: FastAPI กับ SQLAlchemy และ SQLite
FastAPI ไม่ได้มี ORM (Object-Relational Mapping) ของตัวเอง แต่ทำงานร่วมกับ ORM ยอดนิยมอื่น ๆ ได้ดีเยี่ยมครับ ในตัวอย่างนี้เราจะใช้ SQLAlchemy ซึ่งเป็น ORM ที่ทรงพลังและยืดหยุ่น ร่วมกับฐานข้อมูล SQLite (สำหรับการพัฒนา) และไลบรารี `databases` เพื่อรองรับ Asynchronous Database Operations ครับ
ทำความเข้าใจ ORM (Object-Relational Mapping)
ORM เป็นเทคนิคที่ช่วยให้นักพัฒนาสามารถโต้ตอบกับฐานข้อมูลได้โดยใช้ Object-Oriented Programming Language แทนการเขียน SQL โดยตรงครับ
- ข้อดี: ลด boilerplate code, ทำงานกับฐานข้อมูลได้หลากหลาย, ป้องกัน SQL Injection ได้ในระดับหนึ่ง, โค้ดอ่านง่ายขึ้น
- ข้อเสีย: อาจมี Overhead เล็กน้อย, ไม่สามารถใช้ประโยชน์จาก SQL ขั้นสูงได้เต็มที่ถ้าไม่เข้าใจ ORM ดีพอ
ตั้งค่าและเตรียมฐานข้อมูล (SQLite)
1. ติดตั้งไลบรารีที่จำเป็น:
pip install sqlalchemy databases[sqlite]
2. สร้างไฟล์ `database.py` เพื่อตั้งค่าการเชื่อมต่อ:
# database.py
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from databases import Database
# สำหรับ SQLite ในไฟล์
DATABASE_URL = "sqlite:///./sql_app.db"
# สำหรับ SQLAlchemy
engine = create_engine(
DATABASE_URL, connect_args={"check_same_thread": False} # จำเป็นสำหรับ SQLite ใน FastAPI
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# สำหรับ Databases (Async)
database = Database(DATABASE_URL)
3. สร้างไฟล์ `models.py` เพื่อกำหนด Schema ของตาราง (SQLAlchemy Models):
# models.py
from sqlalchemy import Column, Integer, String, Boolean
from .database import Base # import Base จาก database.py
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer) # Foreign Key (ไม่ได้กำหนดในตัวอย่างนี้เพื่อความง่าย)
4. แก้ไข `main.py` เพื่อสร้างตารางและเชื่อมต่อ/ตัดการเชื่อมต่อฐานข้อมูล:
# main.py
from fastapi import FastAPI
from .database import Base, engine, database # import จาก database.py
from . import models # import models.py เพื่อให้ SQLAlchemy รู้จักตาราง
# สร้างตารางในฐานข้อมูล (ถ้ายังไม่มี)
Base.metadata.create_all(bind=engine)
app = FastAPI()
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
# ... Path Operations อื่นๆ ...
สร้าง CRUD Operations (Create, Read, Update, Delete)
เราจะใช้ Dependency Injection เพื่อจัดการ Database Session ครับ
1. เพิ่ม Pydantic Models สำหรับ Request/Response ใน `schemas.py`:
# schemas.py
from pydantic import BaseModel
from typing import List, Optional
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int # ในความเป็นจริงควรมีเจ้าของ
class Config:
orm_mode = True # สำคัญมากสำหรับ SQLAlchemy!
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = [] # User สามารถมีหลาย Items ได้
class Config:
orm_mode = True
2. เพิ่มฟังก์ชัน CRUD ใน `crud.py` (หรือ `dependencies.py`):
# crud.py
from sqlalchemy.orm import Session
from . import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
การใช้ Dependency Injection สำหรับ Database Session
FastAPI มีระบบ Dependency Injection ที่ทรงพลัง ทำให้เราสามารถ “ฉีด” Database Session เข้าไปใน Path Operation Function ได้อย่างง่ายดาย
3. แก้ไข `main.py` อีกครั้งเพื่อเพิ่ม Path Operations สำหรับ User และ Item:
# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from .database import SessionLocal, engine, Base, database
from . import models, schemas, crud # import crud.py
# สร้างตารางในฐานข้อมูล (ถ้ายังไม่มี)
Base.metadata.create_all(bind=engine)
app = FastAPI(
title="SiamLancard FastAPI Database Demo",
description="Building a REST API with FastAPI and SQLAlchemy.",
version="1.0.0",
)
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
# Dependency สำหรับการรับ Database Session
def get_db():
db = SessionLocal()
try:
yield db # ส่ง db session ให้กับ Path Operation Function
finally:
db.close() # ปิด db session เมื่อ Path Operation ทำงานเสร็จ
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
ด้วยวิธีนี้ คุณไม่ต้องกังวลเรื่องการเปิด/ปิด Database Session ด้วยตัวเอง FastAPI และ Dependency Injection จะจัดการให้ครับ และคุณสามารถนำโค้ดที่เกี่ยวข้องกับ Database CRUD ไปรวมไว้ในโมดูล `crud.py` เพื่อให้โค้ดเป็นระเบียบยิ่งขึ้นครับ
อ่านเพิ่มเติมเกี่ยวกับการจัดการฐานข้อมูลใน FastAPI
การจัดการข้อผิดพลาด (Error Handling)
การจัดการข้อผิดพลาดที่ดีเป็นสิ่งสำคัญสำหรับ API ที่แข็งแกร่ง FastAPI มีวิธีจัดการข้อผิดพลาดที่ยืดหยุ่นครับ
การใช้ HTTPException สำหรับข้อผิดพลาดมาตรฐาน
สำหรับข้อผิดพลาด HTTP ทั่วไป เช่น 404 Not Found, 401 Unauthorized, 403 Forbidden, 400 Bad Request คุณสามารถใช้ `HTTPException` ได้โดยตรง
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id % 2 != 0: # สมมติว่า user ID ต้องเป็นเลขคู่
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="User ID must be an even number",
headers={"X-Error-Code": "INVALID_USER_ID"} # สามารถเพิ่ม custom header ได้
)
if user_id > 100: # สมมติว่าไม่มี user ID เกิน 100
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
return {"user_id": user_id, "name": f"User {user_id}"}
FastAPI จะแปลง `HTTPException` ให้เป็น HTTP Response ที่เหมาะสมโดยอัตโนมัติ
การสร้าง Custom Exception Handlers
ในบางกรณี คุณอาจต้องการจัดการข้อผิดพลาดประเภทอื่น ๆ หรือปรับแต่ง Response ของ `HTTPException` เอง คุณสามารถใช้ `@app.exception_handler` ได้
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel
app = FastAPI()
# กำหนด custom exception class
class CustomError(Exception):
def __init__(self, name: str, message: str):
self.name = name
self.message = message
# Handler สำหรับ CustomError
@app.exception_handler(CustomError)
async def custom_error_handler(request: Request, exc: CustomError):
return JSONResponse(
status_code=status.HTTP_418_IM_A_TEAPOT, # ใช้สถานะที่ตลกๆ สำหรับตัวอย่าง
content={"error_name": exc.name, "error_message": exc.message},
)
# Handler สำหรับ HTTPException (เพื่อปรับแต่ง Response)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail, "custom_info": "This is a custom HTTP error response."},
)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 42:
raise CustomError(name="MeaningOfLifeError", message="The item ID 42 is reserved for philosophical discussions.")
if item_id == 99:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access to item 99 is forbidden.")
return {"item_id": item_id, "name": "Item Name"}
ตอนนี้เมื่อคุณเรียก `/items/42` คุณจะได้รับ Response ที่มาจาก `custom_error_handler` และเมื่อเรียก `/items/99` จะได้รับ Response ที่มาจาก `http_exception_handler` ที่เราปรับแต่งไว้ครับ
การตรวจสอบสิทธิ์ (Authentication) และการอนุญาต (Authorization)
การยืนยันตัวตนและการอนุญาตเป็นสิ่งสำคัญสำหรับ API เพื่อควบคุมว่าใครสามารถเข้าถึงข้อมูลหรือฟังก์ชันใดได้บ้าง
แนวคิดพื้นฐานด้านความปลอดภัย
- Authentication (การยืนยันตัวตน): คือกระบวนการพิสูจน์ว่าผู้ใช้คือใคร (เช่น Login ด้วย Username/Password)
- Authorization (การอนุญาต): คือกระบวนการกำหนดว่าผู้ใช้ที่ได้รับการยืนยันตัวตนแล้วมีสิทธิ์ทำอะไรได้บ้าง (เช่น ผู้ดูแลระบบสามารถลบข้อมูลได้, ผู้ใช้ทั่วไปทำได้แค่อ่าน)
FastAPI มีเครื่องมือช่วยในการสร้างระบบความปลอดภัยได้อย่างง่ายดาย โดยใช้ Dependency Injection และ Security Utilities ที่มีมาให้ครับ
การสร้าง API Key Authentication แบบง่าย
เราสามารถใช้ `Security` และ `APIKeyHeader` หรือ `APIKeyQuery` เพื่อสร้างระบบ API Key ได้
from fastapi import FastAPI, Security, HTTPException, status
from fastapi.security import APIKeyHeader
app = FastAPI()
API_KEY = "supersecretapikey"
API_KEY_NAME = "X-API-Key" # ชื่อ Header ที่จะส่ง API Key มา
# สร้าง Security Scheme
api_key_header_auth = APIKeyHeader(name=API_KEY_NAME, auto_error=True)
# Dependency สำหรับตรวจสอบ API Key
async def get_api_key(api_key_header: str = Security(api_key_header_auth)):
if api_key_header == API_KEY:
return api_key_header
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials"
)
@app.get("/secure-data/", dependencies=[Depends(get_api_key)])
async def read_secure_data():
return {"message": "This is highly confidential data!"}
@app.get("/public-data/")
async def read_public_data():
return {"message": "This is public data, no authentication needed."}
เมื่อคุณเรียก `/secure-data/` คุณจะต้องส่ง Header `X-API-Key: supersecretapikey` มาด้วย มิฉะนั้นจะถูกปฏิเสธครับ
การใช้ JWT และ OAuth2PasswordBearer สำหรับระบบ Login
สำหรับระบบ Login ที่มี Username/Password และต้องการ Token (เช่น JWT) เพื่อยืนยันตัวตนใน Request ถัดไป FastAPI มี `OAuth2PasswordBearer` ซึ่งเป็นเครื่องมือที่ใช้ในการจัดการ OAuth2 protocol ครับ
1. ติดตั้งไลบรารีที่จำเป็น:
pip install python-jose[cryptography] passlib[bcrypt]
2. สร้างไฟล์ `auth_utils.py` สำหรับฟังก์ชันช่วยในการเข้ารหัส/ถอดรหัส Token:
# auth_utils.py
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
# กำหนดค่า Secret Key และ Algorithm สำหรับ JWT
SECRET_KEY = "your-super-secret-key" # ควรเก็บใน Environment Variable
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_access_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
return None
3. แก้ไข `main.py` เพื่อเพิ่มระบบ Login และ Protected Endpoint:
# main.py (เพิ่มส่วนนี้เข้าไป)
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from typing import Optional
from .auth_utils import verify_password, create_access_token, decode_access_token, ACCESS_TOKEN_EXPIRE_MINUTES
from datetime import timedelta
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Pydantic Model สำหรับ Token
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
# Mock Database สำหรับผู้ใช้ (ในความเป็นจริงจะดึงจาก DB)
fake_users_db = {
"john.doe": {
"username": "john.doe",
"email": "[email protected]",
"full_name": "John Doe",
"hashed_password": "$2b$12$R.S/Xf.A/O/k0N8b.e0O.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u.e.u", # password: "password123"
"disabled": False,
}
}
def get_user(db, username: str):
if username in db:
return db[username]
return None
async def authenticate_user(username: str, password: str):
user = get_user(fake_users_db, username)
if not user:
return False
if not verify_password(password, user["hashed_password"]):
return False
return user
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
payload = decode_access_token(token)
if payload is None:
raise credentials_exception
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
user = get_user(fake_users_db, token_data.username)
if user is None:
raise credentials_exception
return user
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = await authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me/", response_model=schemas.User) # ควรใช้ schemas.User จากตัวอย่าง DB
async def read_users_me(current_user: dict = Depends(get_current_user)):
# แปลง current_user dict เป็น Pydantic Model ที่เหมาะสม
return schemas.User(
id=1, # สมมติ ID
email=current_user["email"],
is_active=current_user["disabled"], # หรือ is_active
items=[] # หรือดึงจาก DB จริงๆ
)
@app.get("/private-resource/")
async def read_private_resource(current_user: dict = Depends(get_current_user)):
return {"message": f"Hello {current_user['username']}! This is a private resource."}
คุณสามารถใช้ `/token` Endpoint เพื่อ Login ด้วย Username/Password (ลองใช้ `john.doe` / `password123`) เพื่อรับ Access Token มาใช้ใน Request ถัดไปสำหรับ Endpoint ที่ protected โดย `get_current_user` ครับ
อ่านเพิ่มเติมเกี่ยวกับการสร้างระบบยืนยันตัวตนใน FastAPI
การจัดการไฟล์ (File Uploads)
FastAPI ทำให้การจัดการไฟล์ที่อัปโหลดง่ายมาก โดยใช้ `UploadFile` และ `File` ครับ
from fastapi import FastAPI, File, UploadFile
from typing import List
import shutil # สำหรับ save ไฟล์
app = FastAPI()
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
try:
# บันทึกไฟล์ที่อัปโหลดลงในเครื่อง
with open(f"uploaded_{file.filename}", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
finally:
file.file.close() # ต้องปิดไฟล์ที่อัปโหลดมา
return {"filename": file.filename, "content_type": file.content_type}
@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
uploaded_filenames = []
for file in files:
try:
with open(f"uploaded_{file.filename}", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
uploaded_filenames.append(file.filename)
finally:
file.file.close()
return {"message": f"Successfully uploaded {len(uploaded_filenames)} files", "filenames": uploaded_filenames}
@app.post("/upload_with_metadata/")
async def create_upload_file_with_metadata(
description: str,
file: UploadFile = File(...) # File(...) ทำให้เป็น Required Field
):
try:
with open(f"uploaded_{file.filename}", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
finally:
file.file.close()
return {"filename": file.filename, "description": description, "content_type": file.content_type}
คุณสามารถทดสอบ Endpoint เหล่านี้ได้จาก Swagger UI โดยเลือก “Try it out” แล้วเลือกไฟล์ที่ต้องการอัปโหลดครับ
การทำ Asynchronous Operations (Async/Await)
FastAPI ถูกออกแบบมาให้ทำงานร่วมกับ Asynchronous Python (async/await) ได้อย่างเต็มประสิทธิภาพ ซึ่งเหมาะมากสำหรับ I/O-bound operations เช่น การอ่าน/เขียนไฟล์, การเรียกใช้ Database, หรือการเรียก API ภายนอก
เมื่อคุณประกาศ Path Operation Function ด้วย `async def` FastAPI จะรันมันใน Event Loop หลัก ทำให้สามารถจัดการ Request อื่น ๆ ได้ในขณะที่รอ I/O Operation เสร็จสิ้น
from fastapi import FastAPI
import asyncio # สำหรับจำลองงานที่ใช้เวลา
app = FastAPI()
async def fetch_data_from_external_api():
# จำลองการเรียก API ภายนอกที่ใช้เวลา
await asyncio.sleep(2) # รอ 2 วินาที
return {"source": "external_api", "data": "Some fetched data"}
@app.get("/async-data/")
async def get_async_data():
data = await fetch_data_from_external_api()
return {"message": "Data fetched asynchronously", "result": data}
@app.get("/sync-data/")
def get_sync_data(): # ฟังก์ชันนี้เป็น synchronous
import time
time.sleep(2) # blocking call
return {"message": "Data fetched synchronously (blocking)"}
เมื่อคุณเรียก `/async-data/` หลาย ๆ ครั้งพร้อมกัน คุณจะสังเกตได้ว่า Server ยังคงตอบสนองต่อ Request อื่น ๆ ได้รวดเร็ว แต่ถ้าคุณเรียก `/sync-data/` หลาย ๆ ครั้งพร้อมกัน Server อาจจะดูเหมือนค้างไปชั่วขณะ เพราะ `time.sleep()` เป็น Blocking Call ครับ
ข้อควรระวัง: ถ้าคุณมีโค้ดที่เป็น Blocking Call (เช่น ไลบรารีที่ไม่ได้รองรับ `async/await`) และต้องการรันใน `async def` Path Operation, FastAPI จะย้ายมันไปรันใน Thread Pool แยกให้โดยอัตโนมัติ เพื่อไม่ให้ไป Block Event Loop หลัก แต่มันก็ยังดีกว่าการใช้ `def` โดยตรงกับ Blocking Call ในแง่ของ Concurrent Request ครับ
การทดสอบ (Testing) API ของคุณ
การเขียน Unit Test และ Integration Test เป็นสิ่งสำคัญเพื่อให้มั่นใจว่า API ของคุณทำงานได้ถูกต้องและไม่เกิด Regression Bug ครับ FastAPI มีเครื่องมือที่ช่วยให้การทดสอบเป็นเรื่องง่าย
1. ติดตั้งไลบรารีที่จำเป็น:
pip install pytest httpx
2. สร้างไฟล์ `test_main.py` ในโฟลเดอร์โปรเจกต์ของคุณ:
# test_main.py
from fastapi.testclient import TestClient
from main import app # import app instance จากไฟล์ main.py
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Welcome to the FastAPI Demo API!"}
def test_create_item():
response = client.post(
"/items/",
json={"name": "Test Item", "description": "A test item", "price": 10.99},
)
assert response.status_code == 200
assert response.json()["item"]["name"] == "Test Item"
assert "Item created successfully" in response.json()["message"]
def test_read_non_existent_item():
response = client.get("/items/nonexistent") # สมมติว่า item_id เป็น str ใน router
assert response.status_code == 404
assert response.json()["detail"] == "Item not found"
def test_secure_data_unauthorized():
# ทดสอบ Endpoint ที่ต้องการ API Key โดย