
ในโลกของการพัฒนาซอฟต์แวร์ที่ขับเคลื่อนด้วยข้อมูลและบริการที่เชื่อมโยงกัน การสร้าง Application Programming Interface (API) กลายเป็นหัวใจสำคัญที่ช่วยให้ระบบต่างๆ สามารถสื่อสารและแลกเปลี่ยนข้อมูลกันได้อย่างมีประสิทธิภาพครับ และเมื่อพูดถึงการสร้าง RESTful API ด้วย Python ในยุคปัจจุบัน หลายคนคงได้ยินชื่อของ FastAPI ซึ่งเป็นเฟรมเวิร์กที่มาแรง ด้วยประสิทธิภาพที่ยอดเยี่ยม ความเร็วในการพัฒนาที่น่าทึ่ง และระบบเอกสารอัตโนมัติที่ครบครัน บทความนี้จะพาทุกท่านไปเจาะลึกถึงวิธีการสร้าง REST API ด้วย FastAPI ตั้งแต่พื้นฐานไปจนถึงการเชื่อมต่อฐานข้อมูล การจัดการสิทธิ์ และการนำไปใช้งานจริงแบบครบจบทุกขั้นตอน เพื่อให้คุณสามารถนำความรู้ไปประยุกต์ใช้สร้าง API ที่แข็งแกร่งและมีประสิทธิภาพสำหรับโปรเจกต์ของคุณได้อย่างมั่นใจครับ
สารบัญ
- ทำไมต้องสร้าง REST API?
- ทำไมต้องเลือก FastAPI สำหรับการสร้าง API?
- เตรียมความพร้อมก่อนเริ่ม
- สร้าง REST API แรกของคุณด้วย FastAPI
- การจัดการ HTTP Methods
- การใช้ Pydantic สำหรับ Data Validation และ Serialization
- Path Parameters และ Query Parameters
- Dependency Injection: การจัดการการพึ่งพิง
- การจัดการ Error และ Exception
- การเชื่อมต่อฐานข้อมูล
- การทำ Authentication และ Authorization
- การทดสอบ API ด้วย Pytest
- การนำ API ไปใช้งานจริง (Deployment)
- คำถามที่พบบ่อย (FAQ)
- สรุปและ Call-to-Action
ทำไมต้องสร้าง REST API?
ก่อนที่เราจะลงลึกถึงการสร้าง API ด้วย FastAPI เรามาทำความเข้าใจพื้นฐานกันก่อนว่า REST API คืออะไร และทำไมการสร้าง API จึงมีความสำคัญในการพัฒนาแอปพลิเคชันยุคใหม่ครับ
REST API คืออะไร?
REST (Representational State Transfer) ไม่ใช่ภาษาโปรแกรมหรือเฟรมเวิร์ก แต่เป็น สถาปัตยกรรม (Architectural Style) สำหรับการออกแบบระบบเครือข่าย โดยเฉพาะอย่างยิ่ง World Wide Web ครับ ซึ่งถูกคิดค้นโดย Roy Fielding ในปี 2000 เพื่อให้ระบบต่างๆ สามารถสื่อสารกันได้ง่ายและมีมาตรฐาน
หลักการสำคัญของ REST คือการมองทุกสิ่งเป็น Resource (ทรัพยากร) เช่น ผู้ใช้งาน (User), สินค้า (Product), หรือโพสต์ (Post) และแต่ละ Resource จะมี URI (Uniform Resource Identifier) เป็นของตัวเองที่สามารถระบุตัวตนได้ครับ การดำเนินการกับ Resource เหล่านี้จะใช้มาตรฐานของ HTTP Methods ได้แก่:
- GET: สำหรับดึงข้อมูลจาก Resource
- POST: สำหรับสร้าง Resource ใหม่
- PUT: สำหรับอัปเดต Resource ทั้งหมด
- PATCH: สำหรับอัปเดต Resource บางส่วน
- DELETE: สำหรับลบ Resource
ข้อมูลที่ถูกส่งไปมาระหว่าง Client (เช่น เว็บเบราว์เซอร์, โมบายล์แอป) และ Server มักจะอยู่ในรูปแบบ JSON (JavaScript Object Notation) ซึ่งเป็นรูปแบบที่อ่านง่ายและเป็นที่นิยมอย่างแพร่หลายครับ
ประโยชน์ของ REST API
การสร้าง REST API มีประโยชน์มากมายที่ทำให้เป็นที่นิยมในการพัฒนาซอฟต์แวร์ครับ
- มาตรฐานและเข้าใจง่าย: การใช้ HTTP Methods และสถานะ (Status Codes) แบบมาตรฐาน ทำให้ Developer จากทีมต่างๆ สามารถเข้าใจและใช้งาน API ได้ง่าย
- แยกส่วนการทำงาน (Separation of Concerns): REST API ช่วยให้ Frontend (ส่วนแสดงผล) และ Backend (ส่วนประมวลผลข้อมูล) ทำงานแยกจากกันได้อย่างอิสระ ทำให้การพัฒนาและการดูแลรักษาง่ายขึ้น ทีมสามารถทำงานพร้อมกันได้โดยไม่รบกวนกันครับ
- ความยืดหยุ่น (Flexibility): API เดียวสามารถรองรับ Client ได้หลายประเภท ไม่ว่าจะเป็น Web Application, Mobile Application, หรือแม้แต่ IoT Devices
- ความสามารถในการขยายขนาด (Scalability): เนื่องจากเป็น Stateless (เซิร์ฟเวอร์ไม่เก็บข้อมูลสถานะของ Client ระหว่าง Request) ทำให้สามารถเพิ่ม Server เพื่อรองรับ Load ที่เพิ่มขึ้นได้ง่าย
- ประสิทธิภาพ: การใช้ JSON เป็นรูปแบบข้อมูลทำให้มีขนาดเล็กและส่งผ่านเครือข่ายได้อย่างรวดเร็ว
ทำไมต้องเลือก FastAPI สำหรับการสร้าง API?
เมื่อเข้าใจถึงความสำคัญของ REST API แล้ว คำถามต่อไปคือ “ทำไมต้องเป็น FastAPI?” ในขณะที่มีเฟรมเวิร์ก Python อื่นๆ อีกมากมายที่สามารถสร้าง API ได้เช่นกันครับ
FastAPI คืออะไร?
FastAPI เป็นเฟรมเวิร์กเว็บประสิทธิภาพสูงสำหรับสร้าง API ด้วย Python 3.7+ โดยเฉพาะ ถูกออกแบบมาให้ง่ายต่อการใช้งานและรวดเร็วในการพัฒนาครับ FastAPI สร้างขึ้นบนรากฐานของสองไลบรารีที่ทรงพลังคือ:
- Starlette: เฟรมเวิร์ก ASGI (Asynchronous Server Gateway Interface) ขนาดเล็กและเร็วมาก สำหรับการสร้างเว็บเซิร์ฟเวอร์ที่รองรับ Asynchronous I/O (async/await)
- Pydantic: ไลบรารีสำหรับการจัดการข้อมูลด้วย Python type hints ซึ่งช่วยในการตรวจสอบความถูกต้องของข้อมูล (Data Validation) และการจัดรูปแบบข้อมูล (Serialization)
การผสมผสานสองสิ่งนี้เข้าด้วยกัน ทำให้ FastAPI มีความเร็วเทียบเท่ากับ Node.js และ Go ในบางกรณี และมีฟีเจอร์ครบครันที่ช่วยให้การพัฒนา API เป็นเรื่องง่ายและมีประสิทธิภาพครับ
จุดเด่นของ FastAPI
FastAPI มีจุดเด่นหลายประการที่ทำให้เป็นตัวเลือกที่น่าสนใจสำหรับนักพัฒนาครับ
- ประสิทธิภาพสูง: เนื่องจากสร้างบน Starlette และรองรับ Asynchronous I/O ทำให้ FastAPI สามารถจัดการ Request จำนวนมากได้อย่างรวดเร็วครับ
- ง่ายต่อการเรียนรู้และใช้งาน: การใช้ Python type hints ช่วยให้โค้ดสะอาด อ่านง่าย และลดโอกาสเกิดข้อผิดพลาด
- เอกสาร API อัตโนมัติ (Automatic Interactive API Docs): นี่คือหนึ่งในฟีเจอร์ที่โดดเด่นที่สุด FastAPI สร้างเอกสาร API แบบอินเทอร์แอคทีฟให้โดยอัตโนมัติ (Swagger UI และ ReDoc) จากโค้ดของเรา ทำให้ทีม Frontend และผู้ใช้งาน API สามารถเข้าใจและทดสอบ API ได้ทันทีโดยไม่ต้องเขียนเอกสารเพิ่ม
- การตรวจสอบข้อมูล (Data Validation): ด้วย Pydantic, FastAPI สามารถตรวจสอบความถูกต้องของข้อมูลที่เข้ามาใน Request และออก Error ที่ชัดเจนหากข้อมูลไม่ถูกต้อง
- การฉีดการพึ่งพิง (Dependency Injection): ระบบ DI ที่ทรงพลังช่วยให้จัดการโค้ดที่ต้องใช้ร่วมกัน เช่น การเชื่อมต่อฐานข้อมูล หรือการตรวจสอบสิทธิ์ ได้อย่างง่ายดายและเป็นระเบียบ
- รองรับ Asynchronous (Async/Await): สามารถเขียนโค้ดที่ทำงานแบบ Non-blocking I/O ได้ง่ายๆ ช่วยเพิ่มประสิทธิภาพในการจัดการ Request ที่ต้องรอการตอบกลับจากภายนอก
- ความปลอดภัย: มีเครื่องมือสำหรับจัดการความปลอดภัย เช่น การตรวจสอบสิทธิ์ (Authentication) และการอนุญาต (Authorization) ด้วย OAuth2, JWT และอื่นๆ
เปรียบเทียบ FastAPI กับเฟรมเวิร์ก Python อื่นๆ
เพื่อให้เห็นภาพชัดเจนขึ้น เราลองมาดูการเปรียบเทียบ FastAPI กับเฟรมเวิร์ก Python ยอดนิยมอื่นๆ อย่าง Flask และ Django REST Framework ครับ
| คุณสมบัติ | FastAPI | Flask | Django REST Framework (DRF) |
|---|---|---|---|
| แนวคิดหลัก | API-first, Asynchronous, Type-hint driven | Microframework, เน้นความยืดหยุ่น, Minimalist | Full-stack web framework (Django) + API features |
| ประสิทธิภาพ | สูงมาก (สร้างบน Starlette, Pydantic) | ปานกลาง (WSGI), สามารถใช้ Async กับบาง Extension ได้ | ปานกลาง (WSGI), เน้นความครบครัน |
| Data Validation | Built-in (Pydantic), ใช้ Type Hints | ต้องใช้ไลบรารีภายนอก (เช่น Marshmallow) | Built-in (Serializers) |
| เอกสาร API | อัตโนมัติ (Swagger UI, ReDoc) | ต้องใช้ไลบรารีภายนอก หรือเขียนเอง | อัตโนมัติบางส่วน (Browsing API), ต้องใช้ไลบรารีภายนอกสำหรับ Swagger |
| การเรียนรู้ | ปานกลาง-ง่าย (ถ้าคุ้นเคยกับ Type Hints) | ง่าย (Microframework) | ปานกลาง-ยาก (ต้องเรียนรู้ Django ก่อน) |
| Asynchronous | Built-in (Async/Await) | รองรับแบบจำกัด (ต้องใช้ ASGI server และ Extensions) | รองรับแบบจำกัด (เริ่มเพิ่มใน Django 3.x) |
| Dependency Injection | Built-in, ทรงพลัง | ต้องใช้ไลบรารีภายนอก หรือเขียนเอง | มีรูปแบบของตัวเอง (เช่น Middleware) |
| เหมาะสำหรับ | สร้าง RESTful API, Microservices, Real-time services | โปรเจกต์ขนาดเล็ก, REST API ง่ายๆ, Prototype | Web Application ขนาดใหญ่, REST API ที่ซับซ้อน (เมื่อใช้ร่วมกับ Django) |
จากตารางจะเห็นได้ว่า FastAPI มีจุดเด่นในเรื่องประสิทธิภาพ, Data Validation, และการสร้างเอกสารอัตโนมัติ ซึ่งเป็นสิ่งสำคัญสำหรับการสร้าง API ที่มีคุณภาพและรวดเร็วครับ
เตรียมความพร้อมก่อนเริ่ม
ก่อนที่เราจะเริ่มเขียนโค้ดจริง เรามาเตรียมสภาพแวดล้อมการทำงานให้พร้อมกันก่อนนะครับ การเตรียมการที่ดีจะช่วยให้การพัฒนาเป็นไปอย่างราบรื่นครับ
ติดตั้ง Python
FastAPI ต้องการ Python เวอร์ชัน 3.7 ขึ้นไปครับ ถ้าคุณยังไม่มี Python ติดตั้งอยู่ในเครื่อง สามารถดาวน์โหลดได้จากเว็บไซต์ทางการของ Python (python.org) และทำตามขั้นตอนการติดตั้งให้เรียบร้อยครับ
หลังจากติดตั้งเสร็จแล้ว ลองตรวจสอบเวอร์ชันของ Python ใน Command Line หรือ Terminal ของคุณ:
python --version
# หรือบางระบบอาจใช้
python3 --version
คุณควรเห็นผลลัพธ์ประมาณ Python 3.x.x ครับ
สร้าง Virtual Environment
การสร้าง Virtual Environment เป็นแนวทางปฏิบัติที่ดีที่สุดในการพัฒนาโปรเจกต์ Python ครับ เพราะจะช่วยแยก Dependencies ของแต่ละโปรเจกต์ออกจากกัน ไม่ให้รบกวนกันเองครับ
ในโฟลเดอร์โปรเจกต์ของคุณ (เช่น my-fastapi-app) ให้รันคำสั่งต่อไปนี้เพื่อสร้าง Virtual Environment:
mkdir my-fastapi-app
cd my-fastapi-app
python -m venv venv
จากนั้น Activate Virtual Environment:
- บน Windows:
.\venv\Scripts\activate - บน macOS/Linux:
source venv/bin/activate
เมื่อ Virtual Environment ถูก Activate แล้ว คุณจะเห็น (venv) นำหน้า Command Line ของคุณครับ
ติดตั้ง FastAPI และ Uvicorn
ตอนนี้เราพร้อมที่จะติดตั้ง FastAPI และ Uvicorn แล้วครับ
- FastAPI: ตัวเฟรมเวิร์กหลัก
- Uvicorn: ASGI server ที่ใช้รันแอปพลิเคชัน FastAPI (FastAPI ไม่ได้มาพร้อมกับ Server ในตัวเหมือน Django)
รันคำสั่งนี้ในขณะที่ Virtual Environment ของคุณยัง Active อยู่ครับ:
pip install fastapi "uvicorn[standard]"
"uvicorn[standard]" จะติดตั้ง Uvicorn พร้อมกับ Dependencies ที่จำเป็น เช่น httptools และ watchfiles เพื่อประสิทธิภาพที่ดีขึ้นและฟังก์ชันการ Reload อัตโนมัติครับ
คุณสามารถบันทึก Dependencies ของโปรเจกต์ลงในไฟล์ requirements.txt เพื่อให้โปรเจกต์สามารถติดตั้งได้ง่ายในอนาคตครับ:
pip freeze > requirements.txt
สร้าง REST API แรกของคุณด้วย FastAPI
เมื่อเตรียมความพร้อมเรียบร้อยแล้ว ได้เวลาลงมือสร้าง API แรกกันแล้วครับ เราจะเริ่มต้นด้วย API “Hello World” ที่ง่ายที่สุด
โครงสร้างไฟล์เบื้องต้น
ภายในโฟลเดอร์ my-fastapi-app ที่เราสร้างไว้ ให้สร้างไฟล์ชื่อ main.py ขึ้นมาครับ
my-fastapi-app/
├── venv/
└── main.py
Hello World API
เปิดไฟล์ main.py แล้วใส่โค้ดต่อไปนี้ครับ
# main.py
from fastapi import FastAPI
# สร้าง Instance ของ FastAPI
app = FastAPI()
# กำหนด Path Operation Decorator สำหรับ HTTP GET Request ที่ Root URL ("/")
@app.get("/")
async def read_root():
"""
Endpoint สำหรับทดสอบว่า API ทำงานได้หรือไม่
คืนค่าเป็นข้อความ "Hello, World!"
"""
return {"message": "Hello, World!"}
# กำหนด Path Operation Decorator สำหรับ HTTP GET Request ที่ URL "/items/{item_id}"
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
"""
Endpoint สำหรับดึงข้อมูล Item ด้วย item_id
และสามารถใช้ Query Parameter 'q' เพื่อกรองข้อมูลได้
"""
return {"item_id": item_id, "q": q}
คำอธิบายโค้ด:
from fastapi import FastAPI: นำเข้าคลาสFastAPIapp = FastAPI(): สร้าง Instance ของแอปพลิเคชัน FastAPI@app.get("/"): นี่คือ Path Operation Decorator ซึ่งบอก FastAPI ว่าฟังก์ชันread_rootด้านล่างนี้ควรถูกเรียกเมื่อมี HTTP GET Request มาที่ Root URL (/)async def read_root():: การใช้async defแสดงว่าฟังก์ชันนี้เป็นฟังก์ชัน Asynchronous ซึ่งเป็นคุณสมบัติหลักของ FastAPI ที่ช่วยให้ประสิทธิภาพสูงขึ้นครับreturn {"message": "Hello, World!"}: FastAPI จะแปลง Python dictionary นี้ให้เป็น JSON Response โดยอัตโนมัติ- สำหรับ
@app.get("/items/{item_id}"):{item_id}ใน Path เป็น Path Parameter ซึ่งหมายความว่าค่าที่อยู่ในตำแหน่งนี้ของ URL จะถูกส่งเป็น Argument ไปยังฟังก์ชันread_itemitem_id: int: เราใช้ Python type hint (: int) เพื่อบอก FastAPI ว่าitem_idควรจะเป็น Integer FastAPI จะทำการตรวจสอบและแปลงค่าให้โดยอัตโนมัติครับq: str = None: นี่คือ Query Parameter ที่เป็น Optional (ไม่จำเป็นต้องมี) และมีค่า Default เป็นNone
การรัน API ของคุณ
เปิด Command Line/Terminal ในโฟลเดอร์ my-fastapi-app (ตรวจสอบให้แน่ใจว่า Virtual Environment ยัง Active อยู่) แล้วรันคำสั่งนี้ครับ:
uvicorn main:app --reload
คำอธิบายคำสั่ง:
uvicorn: คือ ASGI server ที่เราติดตั้งไปmain:app: บอก Uvicorn ว่าให้โหลดแอปพลิเคชันappจากไฟล์main.py(ชื่อไฟล์คือmain, ชื่อ Instance คือapp)--reload: โหมดนี้จะทำให้ Server รีโหลดโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงโค้ดในไฟล์โปรเจกต์ เหมาะสำหรับการพัฒนาครับ
คุณจะเห็นข้อความคล้ายๆ แบบนี้ใน Terminal:
INFO: Will watch for changes in these directories: ['/path/to/my-fastapi-app']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [xxxxx] using WatchFiles
INFO: Started server process [xxxxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
ตอนนี้ API ของคุณกำลังทำงานอยู่ที่ http://127.0.0.1:8000 ครับ
ทดสอบ API และเอกสารอัตโนมัติ
เปิดเว็บเบราว์เซอร์และลองเข้าถึง URL เหล่านี้:
http://127.0.0.1:8000: คุณควรเห็น{"message": "Hello, World!"}http://127.0.0.1:8000/items/5: คุณควรเห็น{"item_id": 5, "q": null}http://127.0.0.1:8000/items/10?q=somequery: คุณควรเห็น{"item_id": 10, "q": "somequery"}
และที่น่าทึ่งที่สุดคือเอกสาร API อัตโนมัติครับ:
- Swagger UI: เข้าไปที่
http://127.0.0.1:8000/docsคุณจะเห็นหน้าเอกสาร API แบบอินเทอร์แอคทีฟที่สามารถทดสอบ API ได้ทันที - ReDoc: เข้าไปที่
http://127.0.0.1:8000/redocคุณจะเห็นเอกสาร API ที่อ่านง่ายและสวยงามอีกรูปแบบหนึ่ง
นี่คือจุดแข็งสำคัญของ FastAPI ที่ช่วยประหยัดเวลาในการเขียนเอกสารและทำให้การทำงานร่วมกันง่ายขึ้นมากครับ
การจัดการ HTTP Methods
เราได้ลองใช้ HTTP GET ไปแล้ว มาดูกันว่า FastAPI จัดการกับ HTTP Methods อื่นๆ ที่เหลืออย่างไรบ้างครับ
GET: การดึงข้อมูล
สำหรับการดึงข้อมูล เราใช้ @app.get() ครับ เราได้เห็นตัวอย่างไปแล้ว แต่มาดูตัวอย่างเพิ่มเติมเกี่ยวกับการใช้ Path Parameters และ Query Parameters อย่างละเอียดครับ
# main.py (ต่อจากโค้ดเดิม)
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: str, short: bool = False):
"""
ดึงข้อมูล Item ของ User คนใดคนหนึ่ง
- user_id: Path Parameter (int)
- item_id: Path Parameter (str)
- short: Query Parameter (bool) - ถ้าเป็น True จะคืนข้อมูลบางส่วน
"""
item = {"item_id": item_id, "owner_id": user_id}
if not short:
item.update(
{"description": "This is an amazing item that belongs to user "
f"{user_id} and has ID {item_id}"}
)
return item
ในตัวอย่างนี้ เราใช้ Path Parameters สองตัว (user_id และ item_id) และ Query Parameter หนึ่งตัว (short) ที่เป็น Boolean ครับ FastAPI จะทำการแปลงค่าจาก URL ให้เป็น Type ที่ถูกต้องโดยอัตโนมัติ
POST: การสร้างข้อมูล
สำหรับการสร้างข้อมูลใหม่ เราใช้ @app.post() และมักจะส่งข้อมูลในรูปแบบ Request Body ครับ FastAPI ใช้ Pydantic ในการจัดการ Request Body ได้อย่างยอดเยี่ยมครับ
# main.py (ต่อจากโค้ดเดิม)
from typing import Optional
from pydantic import BaseModel
# กำหนด Pydantic Model สำหรับข้อมูล Item
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.post("/items/")
async def create_item(item: Item):
"""
สร้าง Item ใหม่ โดยรับข้อมูล Item จาก Request Body
"""
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
คำอธิบาย:
- เราสร้างคลาส
Itemที่สืบทอดมาจากBaseModelของ Pydantic เพื่อกำหนดโครงสร้างข้อมูลที่เราคาดหวังจาก Request Body name: str: fieldnameเป็น String และต้องมีdescription: Optional[str] = None: fielddescriptionเป็น String แต่เป็น Optional และมีค่า Default เป็นNoneprice: float: fieldpriceเป็น Float และต้องมีtax: Optional[float] = None: fieldtaxเป็น Float แต่เป็น Optional- ใน
create_itemฟังก์ชัน เราประกาศ Argumentitem: Itemซึ่ง FastAPI จะรู้ทันทีว่านี่คือ Request Body และจะใช้ Pydantic ในการตรวจสอบความถูกต้องของข้อมูลที่ส่งมาครับ - ถ้าข้อมูลที่ส่งมาไม่ตรงกับ Model (เช่น ไม่มี
nameหรือprice) FastAPI จะคืนค่า Error Response ที่มีรายละเอียดให้โดยอัตโนมัติ
PUT: การอัปเดตข้อมูลทั้งหมด
สำหรับการอัปเดตข้อมูลทั้งหมดของ Resource ที่มีอยู่ เราใช้ @app.put() ครับ
# main.py (ต่อจากโค้ดเดิม)
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
"""
อัปเดตข้อมูล Item ทั้งหมดสำหรับ item_id ที่ระบุ
"""
results = {"item_id": item_id, **item.dict()}
return results
ในตัวอย่างนี้ เราต้องการอัปเดต Item ด้วย item_id ที่ระบุ โดยรับข้อมูลใหม่ทั้งหมดของ Item จาก Request Body ครับ
PATCH: การอัปเดตข้อมูลบางส่วน
สำหรับการอัปเดตข้อมูลบางส่วน (Partial Update) เราใช้ @app.patch() ครับ ซึ่งมักจะซับซ้อนกว่า PUT เล็กน้อย เพราะต้องจัดการกับ Field ที่ไม่ได้ส่งมา
# main.py (ต่อจากโค้ดเดิม)
# สร้าง Pydantic Model สำหรับการอัปเดตบางส่วน
# กำหนด Field ทั้งหมดเป็น Optional
class ItemUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[float] = None
tax: Optional[float] = None
@app.patch("/items/{item_id}")
async def patch_item(item_id: int, item_update: ItemUpdate):
"""
อัปเดตข้อมูล Item บางส่วนสำหรับ item_id ที่ระบุ
"""
# ในโลกจริง เราจะต้องดึง item ปัจจุบันจากฐานข้อมูลก่อน
# แล้วค่อยอัปเดตเฉพาะ field ที่มีค่าอยู่ใน item_update
# ตัวอย่างจำลอง: ดึง item เดิม (สมมติว่ามีอยู่แล้ว)
existing_item = {"name": "Old Name", "description": "Old description", "price": 100.0, "tax": 10.0}
# อัปเดตเฉพาะ field ที่มีค่าส่งมาใน item_update
updated_data = item_update.dict(exclude_unset=True) # exclude_unset=True จะไม่รวม field ที่ไม่ได้ถูกตั้งค่า
for key, value in updated_data.items():
existing_item[key] = value
return {"item_id": item_id, **existing_item}
เราสร้าง ItemUpdate Model ที่ทุก Field เป็น Optional เพื่อให้ FastAPI ทราบว่าข้อมูลที่ส่งมาอาจมีแค่บางส่วนครับ และใช้ item_update.dict(exclude_unset=True) เพื่อกรองเฉพาะ Field ที่ถูกส่งมาจริงๆ ครับ
DELETE: การลบข้อมูล
สำหรับการลบ Resource เราใช้ @app.delete() ครับ
# main.py (ต่อจากโค้ดเดิม)
from fastapi import HTTPException, status
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
"""
ลบ Item ด้วย item_id ที่ระบุ
"""
# ในโลกจริง เราจะทำการลบ item จากฐานข้อมูล
# ตัวอย่างจำลอง: ตรวจสอบว่า item_id มีอยู่จริงหรือไม่
if item_id not in [1, 2, 3]: # สมมติว่ามีแค่ item_id 1, 2, 3
raise HTTPException(status_code=404, detail="Item not found")
# ถ้าลบสำเร็จ ก็ไม่ต้องคืนค่าอะไร แต่กำหนด Status Code เป็น 204 No Content
return
เรากำหนด status_code=status.HTTP_204_NO_CONTENT ซึ่งเป็น Status Code มาตรฐานสำหรับ HTTP DELETE ที่สำเร็จโดยไม่มีการคืน Content ใดๆ ครับ และมีการใช้ HTTPException เพื่อจัดการกรณีที่ Item ไม่พบด้วย
การใช้ Pydantic สำหรับ Data Validation และ Serialization
Pydantic เป็นหัวใจสำคัญที่ทำให้ FastAPI ทรงพลังในเรื่องการจัดการข้อมูลครับ เราได้เห็นการใช้งาน Pydantic BaseModel ไปบ้างแล้วในส่วนของ HTTP POST แต่มาทำความเข้าใจให้ลึกซึ้งยิ่งขึ้นครับ
การสร้าง Models
Pydantic Models ช่วยให้เรากำหนดโครงสร้างข้อมูลที่คาดหวังได้อย่างชัดเจน โดยใช้ Python type hints ครับ
# models.py (สร้างไฟล์ใหม่เพื่อจัดระเบียบโค้ด)
from typing import Optional, List
from pydantic import BaseModel, Field, HttpUrl
class UserBase(BaseModel):
username: str
email: str
full_name: Optional[str] = None
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool = True # สามารถกำหนดค่า default ได้
items: List['Item'] = [] # สามารถอ้างอิง Model อื่นๆ ได้ (ต้องเป็น string ถ้ายังไม่ได้ประกาศ)
class Config:
orm_mode = True # สำคัญมากสำหรับการเชื่อมต่อกับ ORM (เช่น SQLAlchemy)
class ItemBase(BaseModel):
title: str = Field(..., example="The amazing item") # ... หมายถึง Required, example สำหรับ Docs
description: Optional[str] = Field(None, example="A very useful item")
price: float = Field(..., gt=0, description="Price must be greater than zero") # gt = greater than
tags: List[str] = []
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
# ต้องประกาศหลังจาก Item เพื่อให้ User สามารถอ้างอิงได้
User.update_forward_refs()
คำอธิบาย:
BaseModel: คลาสหลักที่ใช้สร้าง Pydantic ModelField(...): ใช้สำหรับกำหนดคุณสมบัติเพิ่มเติมของ Field เช่นexampleสำหรับเอกสาร,gt(greater than) สำหรับการตรวจสอบค่าOptional[str] = None: ระบุว่า Field นั้นเป็น Optional และมีค่า Default เป็นNoneList['Item'] = []: สามารถกำหนด Field ที่เป็น List ของ Model อื่นๆ ได้ การใช้'Item'เป็น String แทนที่จะเป็นItemโดยตรง เป็นวิธีที่เรียกว่า “Forward Reference” เพื่อหลีกเลี่ยง Circular Dependency ในกรณีที่ Models อ้างอิงถึงกันครับclass Config: orm_mode = True: Property นี้สำคัญมากเมื่อเราจะเชื่อมต่อกับฐานข้อมูล โดยเฉพาะกับ ORM อย่าง SQLAlchemy ครับ มันจะบอก Pydantic ให้พยายามอ่านข้อมูลจาก Attributes ของ Object แทนที่จะเป็น Dictionary Keys ทำให้เราสามารถส่ง SQLAlchemy Model Object เข้าไปใน Pydantic Model ได้โดยตรงUser.update_forward_refs(): จำเป็นต้องเรียกเพื่อแก้ไข Forward References หลังจากที่ Model ทั้งหมดถูกประกาศแล้วครับ
การใช้งานใน Request Body
เราได้เห็นไปแล้วว่าสามารถประกาศ Pydantic Model เป็น Type ของ Argument ใน Path Operation Function ได้ครับ
# main.py (ส่วนหนึ่ง)
from .models import ItemCreate # สมมติว่า models.py อยู่ในโฟลเดอร์เดียวกัน
@app.post("/items/", response_model=Item) # กำหนด response_model ด้วย
async def create_item(item: ItemCreate):
# ตรรกะการสร้าง item ในฐานข้อมูล
# ...
fake_item_id = len(fake_db["items"]) + 1 # สร้าง ID จำลอง
db_item = {"id": fake_item_id, "owner_id": 1, **item.dict()}
fake_db["items"].append(db_item)
return db_item # คืนค่าเป็น dictionary ที่จะถูกแปลงเป็น Item Model โดยอัตโนมัติ
การใช้งานใน Response Model
FastAPI ยังสามารถใช้ Pydantic Model เพื่อกำหนดโครงสร้างของ Response ที่จะส่งกลับไปยัง Client ได้ด้วยการใช้ Argument response_model ใน Path Operation Decorator ครับ
# main.py (ส่วนหนึ่ง)
from .models import Item # นำเข้า Item Model
# ...
@app.get("/items/{item_id}", response_model=Item)
async def read_item_by_id(item_id: int):
"""
ดึงข้อมูล Item ด้วย item_id และคืนค่าเป็น Item Model
"""
# สมมติว่าดึงมาจากฐานข้อมูล
fake_db = {"items": [{"id": 1, "title": "Foo", "description": "Desc1", "price": 10.0, "tags": ["tag1"], "owner_id": 1}]}
for item in fake_db["items"]:
if item["id"] == item_id:
return item
raise HTTPException(status_code=404, detail="Item not found")
ประโยชน์ของ response_model:
- Data Filtering: ถ้าคุณคืนค่า Object ที่มี Field มากกว่าที่
response_modelกำหนด FastAPI จะส่งคืนเฉพาะ Field ที่อยู่ในresponse_modelเท่านั้น (ช่วยในการซ่อนข้อมูลที่ไม่จำเป็น) - Data Type Conversion: FastAPI จะตรวจสอบและแปลงข้อมูลให้ตรงกับ Type ที่กำหนดใน
response_model - Automatic Documentation: เอกสาร API จะแสดงโครงสร้างของ Response ที่ถูกต้องตาม Pydantic Model
การใช้ Pydantic ช่วยให้โค้ดของคุณมีความปลอดภัย มีประสิทธิภาพ และมีเอกสารประกอบที่ชัดเจนโดยไม่ต้องเสียเวลาเขียนเองครับ
Path Parameters และ Query Parameters
เราได้แตะเรื่องนี้ไปบ้างแล้ว แต่มาเจาะลึกเพิ่มเติมเกี่ยวกับการใช้งาน Path Parameters และ Query Parameters ใน FastAPI ครับ
Path Parameters
Path Parameters ใช้สำหรับระบุ Resource ที่เฉพาะเจาะจง มักเป็นส่วนหนึ่งของ URL Path ครับ
# main.py (ตัวอย่าง)
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(..., title="The ID of the item to get", ge=1) # ge = greater than or equal
):
"""
ดึงข้อมูล Item โดยใช้ Item ID ที่ต้องเป็น Integer และมากกว่าหรือเท่ากับ 1
"""
return {"item_id": item_id}
คำอธิบาย:
Path(...): ใช้สำหรับกำหนด Metadata และ Validation เพิ่มเติมให้กับ Path Parameter...: หมายถึง Required (จำเป็นต้องมี)title,description,example: ใช้สำหรับเอกสาร Swagger UIge=1(greater than or equal): กำหนดเงื่อนไขว่าitem_idต้องมีค่ามากกว่าหรือเท่ากับ 1
FastAPI จะทำการตรวจสอบความถูกต้องของค่า Path Parameter ตามเงื่อนไขเหล่านี้ให้โดยอัตโนมัติครับ
Query Parameters
Query Parameters ใช้สำหรับกรอง, จัดเรียง, หรือกำหนดตัวเลือกเพิ่มเติมในการดึงข้อมูล มักจะปรากฏหลังเครื่องหมาย ? ใน URL ครับ
# main.py (ตัวอย่าง)
from typing import Optional, List
from fastapi import Query
@app.get("/items/")
async def read_items(
q: Optional[str] = Query(
None,
min_length=3,
max_length=50,
title="Query string",
description="Query string for the items to search in the database that have a good match",
alias="item-query" # กำหนดชื่อ Query Parameter ใน URL เป็น 'item-query' แทน 'q'
),
limit: int = Query(10, gt=0, le=100), # ค่า default คือ 10, ต้อง > 0 และ <= 100
tags: List[str] = Query([]) # รับ Query Parameter หลายค่า เช่น /items/?tags=food&tags=drink
):
"""
ดึงรายการ Item พร้อม Query Parameters สำหรับการค้นหา, จำกัดจำนวน, และกรองตาม Tags
"""
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
if limit:
results.update({"limit": limit})
if tags:
results.update({"tags": tags})
return results
คำอธิบาย:
Query(None, ...): ใช้สำหรับกำหนด Metadata และ Validation เพิ่มเติมให้กับ Query Parametermin_length,max_length: กำหนดความยาวของ Stringalias="item-query": ให้ Query Parameter ใน URL เป็นitem-queryแต่ในโค้ดเรายังคงใช้ชื่อqได้ครับList[str] = Query([]): แสดงถึง Query Parameter ที่สามารถรับค่าได้หลายครั้ง เช่น/items/?tags=tag1&tags=tag2
การใช้ Path และ Query ร่วมกัน
คุณสามารถใช้ Path Parameters และ Query Parameters ร่วมกันใน Path Operation เดียวกันได้ครับ
# main.py (ตัวอย่าง)
@app.get("/users/{user_id}/orders")
async def get_user_orders(
user_id: int = Path(..., description="The ID of the user"),
status: Optional[str] = Query(None, description="Filter orders by status")
):
"""
ดึงรายการคำสั่งซื้อของ User ที่ระบุ โดยสามารถกรองตามสถานะได้
"""
orders = [{"id": 1, "item": "Laptop", "status": "shipped"}, {"id": 2, "item": "Mouse", "status": "pending"}]
if status:
orders = [order for order in orders if order["status"] == status]
return {"user_id": user_id, "orders": orders}
การใช้ Path และ Query Parameters อย่างเหมาะสมช่วยให้ API มีความยืดหยุ่นและชัดเจนในการใช้งานครับ
Dependency Injection: การจัดการการพึ่งพิง
Dependency Injection (DI) เป็นหนึ่งในคุณสมบัติที่ทรงพลังที่สุดของ FastAPI ที่ช่วยให้การจัดการโค้ดที่ซับซ้อน เช่น การเชื่อมต่อฐานข้อมูล การตรวจสอบสิทธิ์ หรือการนำเข้าบริการต่างๆ ทำได้อย่างเป็นระเบียบและง่ายต่อการทดสอบครับ
แนวคิดของ Dependency Injection
แนวคิดหลักคือแทนที่จะให้ฟังก์ชันหรือคลาสสร้าง Dependencies (สิ่งที่ต้องพึ่งพิง) ด้วยตัวเอง เราจะ "ฉีด" (Inject) Dependencies เหล่านั้นเข้าไปให้ครับ
สมมติว่าคุณมีฟังก์ชันที่ต้องการเชื่อมต่อฐานข้อมูล ทุกครั้งที่ฟังก์ชันถูกเรียก คุณอาจจะสร้าง Connection ใหม่ ซึ่งไม่เหมาะสมครับ แต่ถ้าเรามีฟังก์ชัน get_db_session() ที่สร้าง Connection ให้ และเราแค่บอกฟังก์ชันหลักว่า "ฉันต้องการ DB Session นะ" แล้ว FastAPI จะจัดการเรียก get_db_session() และส่งค่ากลับมาให้เอง นี่คือ Dependency Injection ครับ
การสร้างและใช้งาน Dependency
เราใช้ฟังก์ชัน Depends จาก fastapi เพื่อบอกว่า Argument ของ Path Operation Function เป็น Dependency ครับ
# main.py (ตัวอย่าง)
from fastapi import FastAPI, Depends, Header, HTTPException, status
from typing import Optional
app = FastAPI()
# Dependency: ตรวจสอบ Header ที่กำหนด
async def verify_token(x_token: str = Header(...)):
"""
Dependency สำหรับตรวจสอบ Token ใน HTTP Header
"""
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid X-Token header")
return x_token
# Dependency: รับ User ID (จำลอง)
async def get_current_user(token: str = Depends(verify_token)):
"""
Dependency สำหรับดึงข้อมูล User จาก Token (จำลอง)
"""
# ในโลกจริง จะถอดรหัส token เพื่อดึง user_id
fake_users_db = {
"fake-super-secret-token": {"username": "johndoe", "email": "[email protected]", "id": 1}
}
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials")
return user
@app.get("/users/me/")
async def read_users_me(current_user: dict = Depends(get_current_user)):
"""
ดึงข้อมูล User ปัจจุบัน (ต้องผ่านการตรวจสอบ Token)
"""
return current_user
@app.get("/items_protected/")
async def read_protected_items(current_user: dict = Depends(get_current_user)):
"""
ดึงข้อมูล Item ที่ต้องมีการตรวจสอบสิทธิ์
"""
return [
{"item_id": "Foo", "owner": current_user["username"]},
{"item_id": "Bar", "owner": current_user["username"]},
]
คำอธิบาย:
verify_tokenเป็น Dependency ที่ตรวจสอบX-TokenHeaderget_current_userเป็น Dependency ที่ พึ่งพาverify_tokenอีกที (ใช้token: str = Depends(verify_token)) และดึงข้อมูล Userread_users_meและread_protected_itemsเป็น Path Operation ที่พึ่งพาget_current_user- FastAPI จะจัดการเรียก Dependencies ตามลำดับ และส่งค่าที่ได้กลับมาเป็น Argument ให้กับฟังก์ชันหลัก
- ถ้า Dependency ใดๆ ยก
HTTPException, Request จะหยุดลงและคืนค่า Error นั้นทันที
Dependency Injection ทำให้โค้ดโมดูลาร์ขึ้น, ทดสอบง่ายขึ้น, และนำกลับมาใช้ใหม่ได้ง่ายขึ้นมากๆ ครับ
การจัดการ Error และ Exception
การจัดการข้อผิดพลาด (Error Handling) เป็นสิ่งสำคัญในการสร้าง API ที่แข็งแกร่งและใช้งานง่ายครับ FastAPI มีกลไกในการจัดการ Exception ที่ชัดเจนและมีประสิทธิภาพ
HTTP Exceptions (Built-in)
FastAPI มีคลาส HTTPException ในตัวที่คุณสามารถใช้เพื่อส่ง HTTP Error Response ที่มี Status Code และ Detail Message ที่กำหนดเองได้ครับ
# main.py (ตัวอย่าง)
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
"""
ดึงข้อมูล User โดย user_id
ถ้าไม่พบจะคืนค่า HTTP 404 Not Found
"""
# สมมติว่าดึงมาจากฐานข้อมูล
fake_users_db = {
1: {"username": "johndoe"},
2: {"username": "janedoe"},
}
user = fake_users_db.get(user_id)
if user is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, # 404
detail="User not found"
)
return user
@app.post("/items_limited/")
async def create_item_limited(name: str):
"""
สร้าง Item แต่จำกัดความยาวชื่อ
ถ้าชื่อยาวเกินไปจะคืนค่า HTTP 400 Bad Request
"""
if len(name) > 10:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, # 400
detail="Item name is too long (max 10 characters)"
)
return {"name": name, "status": "created"}
เมื่อ HTTPException ถูกยกขึ้น FastAPI จะสร้าง JSON Response ที่มี "detail" field และ HTTP Status Code ที่คุณระบุให้โดยอัตโนมัติครับ
Custom Exception Handlers
บางครั้งคุณอาจต้องการจัดการกับ Exception ชนิดอื่นที่ไม่ใช่ HTTPException (เช่น Database Connection Error, Custom Application Error) หรือต้องการปรับแต่งรูปแบบ Response ของ HTTPException เองครับ คุณสามารถทำได้โดยการสร้าง Custom Exception Handler
# main.py (ตัวอย่าง)
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
# สร้าง Custom Exception
class UnicornException(Exception):
def __init__(self, name: str):
self.name = name
# สร้าง Exception Handler สำหรับ Custom Exception
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=status.HTTP_418_IM_A_TEAPOT, # ตัวอย่าง Status Code ที่ไม่ธรรมดา
content={"message": f"Oops! {exc.name} did something wrong. It's a teapot."},
)
# ตัวอย่างการ Handle HTTPException ที่เกิดจาก Validation Error (หรืออื่นๆ)
@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException):
# คุณสามารถปรับแต่ง Response ของ HTTPException ได้ที่นี่
return JSONResponse(
status_code=exc.status_code,
content={"error_type": "HTTP Error", "message": exc.detail},
)
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
raise UnicornException(name=name)
return {"unicorn_name": name}
คำอธิบาย:
- เราสร้างคลาส
UnicornExceptionขึ้นมาเอง - เราใช้
@app.exception_handler(UnicornException)เพื่อลงทะเบียนฟังก์ชันunicorn_exception_handlerให้เป็นตัวจัดการเมื่อUnicornExceptionถูกยกขึ้น request: Request: Argument นี้ให้คุณเข้าถึงข้อมูล Request ได้exc: UnicornException: Argument นี้คือ Instance ของ Exception ที่ถูกยกขึ้น- เราคืนค่า
JSONResponseที่มี Status Code และ Content ที่เราต้องการ - สำหรับ
StarletteHTTPException(ซึ่งเป็นคลาสแม่ของ FastAPIHTTPException) เราสามารถใช้ Handler เพื่อปรับแต่ง Default Error Response ได้ครับ
การจัดการ Exception ที่ดีช่วยให้ API ของคุณมีความทนทานและให้ข้อมูลที่เป็นประโยชน์แก่ Client เมื่อเกิดข้อผิดพลาดครับ
การเชื่อมต่อฐานข้อมูล
API ส่วนใหญ่ไม่ได้ทำงานอยู่โดดๆ แต่ต้องมีการจัดเก็บและดึงข้อมูลจากฐานข้อมูลครับ ในส่วนนี้ เราจะมาดูวิธีการเชื่อมต่อ FastAPI กับฐานข้อมูล โดยจะใช้ SQLAlchemy ซึ่งเป็น ORM (Object-Relational Mapper) ยอดนิยมของ Python และ SQLite เป็นฐานข้อมูลตัวอย่างครับ
เลือกฐานข้อมูล
ก่อนอื่นต้องเลือกฐานข้อมูลที่เหมาะสมกับโปรเจกต์ของคุณครับ
- SQL Databases (Relational Databases): เช่น PostgreSQL, MySQL, SQLite, SQL Server เหมาะสำหรับข้อมูลที่มีโครงสร้างชัดเจนและต้องการความสัมพันธ์ที่ซับซ้อน
- NoSQL Databases (Non-relational Databases): เช่น MongoDB, Cassandra, Redis เหมาะสำหรับข้อมูลที่ไม่มีโครงสร้างตายตัว หรือต้องการความเร็วในการอ่าน/เขียนสูง
ในตัวอย่างนี้ เราจะใช้ SQLite ซึ่งเป็นฐานข้อมูลแบบไฟล์ ไม่ต้องติดตั้ง Server เพิ่มเติม เหมาะสำหรับการเริ่มต้นและการพัฒนาครับ
ตัวอย่างการเชื่อมต่อกับ SQLAlchemy และ SQLite
เราจะจัดโครงสร้างโปรเจกต์ให้เป็นระเบียบมากขึ้นเพื่อรองรับการทำงานกับฐานข้อมูลครับ
my-fastapi-app/
├── venv/
├── main.py
├── database.py # ตั้งค่าการเชื่อมต่อฐานข้อมูล
├── models.py # กำหนด SQLAlchemy Models (โครงสร้างตาราง)
├── schemas.py # กำหนด Pydantic Models (โครงสร้างข้อมูลสำหรับ API)
└── crud.py # ฟังก์ชันสำหรับ Create, Read, Update, Delete (CRUD)
ก่อนอื่น ติดตั้งไลบรารีที่จำเป็นครับ:
pip install sqlalchemy "pydantic[email]" # pydantic[email] สำหรับ email validation
# เราไม่ได้ใช้ database driver เพิ่มเติมสำหรับ SQLite เพราะเป็น built-in ของ Python
ไฟล์ schemas.py (Pydantic Models)
ไฟล์นี้จะเก็บ Pydantic Models ที่เราใช้สำหรับ Request Body และ Response Model แยกออกจาก SQLAlchemy Models เพื่อความชัดเจนและยืดหยุ่น
# schemas.py
from typing import List, Optional
from pydantic import BaseModel, EmailStr
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 Object เป็น Pydantic Model
class UserBase(BaseModel):
email: EmailStr
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = [] # User มี List ของ Item
class Config:
orm_mode = True
ไฟล์ database.py
ไฟล์นี้จะจัดการการเชื่อมต่อกับฐานข้อมูลและสร้าง Session ครับ
# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# URL สำหรับเชื่อมต่อ SQLite (จะสร้างไฟล์ database.db ในโฟลเดอร์เดียวกัน)
SQLALCHEMY_DATABASE_URL = "sqlite:///./database.db"
# สร้าง SQLAlchemy Engine
# connect_args={"check_same_thread": False} จำเป็นสำหรับ SQLite เพื่อให้ทำงานกับหลาย Request พร้อมกันได้
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# สร้าง SessionLocal class สำหรับการสร้าง Database Session
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Base class ที่ SQLAlchemy Models จะสืบทอดไป
Base = declarative_base()
# Dependency สำหรับการสร้างและปิด Database Session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
ไฟล์ models.py (SQLAlchemy Models)
ไฟล์นี้จะกำหนดโครงสร้างของตารางในฐานข้อมูลโดยใช้ SQLAlchemy ครับ
# models.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base # นำเข้า 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)
# ความสัมพันธ์กับ Item (One-to-Many: User หนึ่งคนมี Item ได้หลายชิ้น)
items = relationship("Item", back_populates="owner")
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, ForeignKey("users.id")) # Foreign Key
# ความสัมพันธ์กับ User (Many-to-One: Item หนึ่งชิ้นมี Owner เป็น User หนึ่งคน)
owner = relationship("User", back