
สวัสดีครับทุกท่านที่หลงใหลในโลกของการพัฒนาซอฟต์แวร์และเทคโนโลยี! ในยุคดิจิทัลที่ทุกสิ่งเชื่อมโยงกัน การสร้าง API (Application Programming Interface) เปรียบเสมือนการสร้างสะพานเชื่อมให้แอปพลิเคชันต่าง ๆ สามารถสื่อสารและแลกเปลี่ยนข้อมูลกันได้อย่างราบรื่น และเมื่อพูดถึงการสร้าง REST API ด้วย Python ในปัจจุบัน ชื่อของ FastAPI ก็มักจะถูกเอ่ยถึงเป็นอันดับต้น ๆ ด้วยความเร็ว ประสิทธิภาพ และความง่ายในการใช้งานที่โดดเด่นครับ
บทความนี้จะพาคุณดำดิ่งสู่โลกของ FastAPI ตั้งแต่การทำความเข้าใจพื้นฐาน ไปจนถึงการสร้าง REST API แบบครบวงจรที่พร้อมใช้งานจริง ไม่ว่าคุณจะเป็นนักพัฒนาหน้าใหม่ที่อยากเริ่มต้น หรือนักพัฒนามากประสบการณ์ที่ต้องการยกระดับทักษะ บทความนี้จะมอบแนวทางและตัวอย่างโค้ดที่ชัดเจน ครบถ้วน และใช้งานได้จริง ให้คุณสามารถสร้าง API ที่ทรงพลังและมีประสิทธิภาพได้อย่างมั่นใจครับ มาเริ่มกันเลย!
สารบัญ
- 1. ทำความรู้จักกับ REST API และ FastAPI
- 2. เตรียมความพร้อมก่อนเริ่มสร้าง API
- 3. สร้าง REST API แรกของคุณด้วย FastAPI
- 4. หัวใจของ REST API: HTTP Methods และ Path Operations
- 5. การจัดการข้อมูลด้วย Pydantic และ Request Body
- 6. Query Parameters และ Path Parameters
- 7. การเชื่อมต่อกับฐานข้อมูล (Database Integration)
- 8. การจัดการข้อผิดพลาด (Error Handling)
- 9. การยืนยันตัวตนและการอนุญาต (Authentication & Authorization)
- 10. การทดสอบ API (Testing Your FastAPI Application)
- 11. การปรับใช้ API สู่ Production (Deployment)
- 12. เทคนิคและเครื่องมือเพิ่มเติม (Advanced Topics)
- คำถามที่พบบ่อย (FAQ) เกี่ยวกับ FastAPI
- สรุปและ Call-to-Action
1. ทำความรู้จักกับ REST API และ FastAPI
REST API คืออะไร?
ก่อนที่เราจะลงมือสร้าง API กัน เรามาทำความเข้าใจพื้นฐานกันก่อนว่า REST API คืออะไรครับ
REST (Representational State Transfer) ไม่ใช่ภาษาโปรแกรมหรือไลบรารี แต่เป็น สถาปัตยกรรม (Architectural Style) สำหรับการออกแบบระบบเครือข่ายไร้สถานะ (Stateless Networked Applications) ครับ เป้าหมายหลักคือการทำให้ระบบสามารถสื่อสารกันได้ง่ายขึ้น มีประสิทธิภาพ และปรับขนาดได้ดี โดยเฉพาะอย่างยิ่งในยุคของเว็บและโมบายล์แอปพลิเคชัน
หลักการสำคัญของ REST API มีดังนี้ครับ:
- Client-Server: แยกส่วน Frontend (Client) และ Backend (Server) ออกจากกันอย่างชัดเจน ทำให้แต่ละส่วนสามารถพัฒนาได้อย่างอิสระ
- Stateless: เซิร์ฟเวอร์จะไม่เก็บสถานะใด ๆ ของไคลเอนต์ระหว่างคำขอแต่ละครั้ง แต่ละคำขอจากไคลเอนต์จะต้องมีข้อมูลที่จำเป็นครบถ้วนในตัวเอง ซึ่งช่วยให้ระบบมีความยืดหยุ่นและปรับขนาดได้ง่าย
- Cacheable: การตอบกลับจากเซิร์ฟเวอร์สามารถระบุได้ว่าข้อมูลนั้นสามารถแคชได้หรือไม่ เพื่อปรับปรุงประสิทธิภาพการทำงาน
- Layered System: ระบบสามารถมีเลเยอร์ (ชั้น) หลายชั้นได้ เช่น Proxy, Load Balancer โดยที่ไคลเอนต์ไม่จำเป็นต้องรู้ว่ากำลังสื่อสารกับเซิร์ฟเวอร์โดยตรงหรือไม่
- Uniform Interface: เป็นหัวใจสำคัญของ REST โดยกำหนดวิธีการมาตรฐานในการสื่อสาร ซึ่งประกอบด้วย:
- Resource Identification: ทรัพยากรแต่ละอย่างมี URI (Uniform Resource Identifier) เฉพาะตัว เช่น
/users,/products/123 - Resource Manipulation: ไคลเอนต์สามารถจัดการทรัพยากรผ่าน Representation ของมันได้ (เช่น ส่ง JSON เพื่อสร้างหรืออัปเดตข้อมูล)
- Self-Descriptive Messages: ข้อความที่ส่งหากันควรมีข้อมูลเพียงพอที่จะอธิบายตัวเองได้
- Hypermedia as the Engine of Application State (HATEOAS): เซิร์ฟเวอร์สามารถส่งลิงก์ (hyperlinks) ในการตอบกลับ เพื่อให้ไคลเอนต์รู้ว่าสามารถทำอะไรต่อไปได้บ้าง (ส่วนนี้มักไม่ค่อยถูกนำมาใช้เต็มรูปแบบใน API ทั่วไป แต่เป็นหลักการหนึ่งของ REST ที่สมบูรณ์แบบครับ)
- Resource Identification: ทรัพยากรแต่ละอย่างมี URI (Uniform Resource Identifier) เฉพาะตัว เช่น
REST API มักจะใช้ HTTP Methods (GET, POST, PUT, DELETE, PATCH) ในการระบุการดำเนินการกับทรัพยากรต่าง ๆ และใช้รูปแบบข้อมูลที่เป็นมาตรฐาน เช่น JSON (นิยมที่สุด), XML หรือ YAML ในการแลกเปลี่ยนข้อมูลครับ
ทำไมต้อง FastAPI? ข้อดีที่ไม่ควรมองข้าม
เมื่อเราเข้าใจ REST API แล้ว คำถามต่อไปคือ “ทำไมเราควรเลือก FastAPI?” ครับ ในโลกของ Python มีเฟรมเวิร์กสำหรับสร้างเว็บและ API มากมาย เช่น Django, Flask, Pyramid แต่ FastAPI ได้รับความนิยมอย่างก้าวกระโดดในช่วงไม่กี่ปีที่ผ่านมา ด้วยข้อดีที่โดดเด่นหลายประการครับ
-
Performance ที่ยอดเยี่ยม:
FastAPI ถูกสร้างขึ้นบน Starlette (สำหรับเว็บ) และ Pydantic (สำหรับการจัดการข้อมูล) ซึ่งเป็นไลบรารีที่มีประสิทธิภาพสูงมาก ทำให้ FastAPI เป็นหนึ่งใน Python เฟรมเวิร์กที่เร็วที่สุด เทียบเท่าหรือเร็วกว่า Node.js และ Go เลยทีเดียวครับ นี่เป็นปัจจัยสำคัญสำหรับแอปพลิเคชันที่ต้องการความเร็วสูงและสามารถรองรับปริมาณงานจำนวนมากได้
-
ใช้งานง่ายและรวดเร็วในการพัฒนา:
FastAPI ใช้ Type Hinting ของ Python อย่างเต็มที่ ทำให้โค้ดมีความชัดเจน อ่านง่าย และสามารถตรวจสอบข้อผิดพลาดได้ตั้งแต่ขั้นตอนการเขียนโค้ด (IDE support) นอกจากนี้ การใช้ Pydantic ในการกำหนดรูปแบบข้อมูลยังช่วยลดโค้ด boilerplate ลงไปได้อย่างมาก ทำให้คุณสามารถสร้าง API ได้เร็วขึ้นหลายเท่าครับ
-
Data Validation และ Serialization อัตโนมัติ (ด้วย Pydantic):
นี่คือจุดแข็งสำคัญที่สุดอย่างหนึ่งของ FastAPI ครับ เมื่อคุณกำหนด Type Hinting ให้กับพารามิเตอร์ของฟังก์ชันหรือ Pydantic Model, FastAPI จะทำการตรวจสอบความถูกต้องของข้อมูล (Validation) และแปลงข้อมูล (Serialization/Deserialization) ให้คุณโดยอัตโนมัติ ทำให้คุณไม่ต้องเขียนโค้ดตรวจสอบข้อมูลซ้ำ ๆ และมั่นใจได้ว่าข้อมูลที่เข้าสู่ API ของคุณเป็นไปตามรูปแบบที่ต้องการครับ
-
OpenAPI (Swagger UI/ReDoc) อัตโนมัติ:
เมื่อคุณสร้าง API ด้วย FastAPI ตัวเฟรมเวิร์กจะสร้างเอกสาร API ที่เป็นมาตรฐาน OpenAPI (เดิมคือ Swagger) ให้คุณโดยอัตโนมัติครับ ซึ่งคุณสามารถเข้าถึงได้ผ่านหน้าเว็บแบบโต้ตอบ (Interactive Documentation) อย่าง Swagger UI (
/docs) และ ReDoc (/redoc) ได้ทันที ทำให้การทดสอบ API และการสื่อสารกับนักพัฒนาคนอื่น ๆ ทำได้ง่ายและมีประสิทธิภาพอย่างยิ่งครับ -
Asynchronous Support (
async/await):FastAPI รองรับการทำงานแบบ Asynchronous เต็มรูปแบบ ทำให้คุณสามารถเขียนโค้ดที่จัดการกับการ I/O-bound operations (เช่น การอ่าน/เขียนฐานข้อมูล, การเรียก API ภายนอก) ได้อย่างมีประสิทธิภาพ โดยไม่บล็อกการทำงานหลักของเซิร์ฟเวอร์ ซึ่งช่วยเพิ่ม Throughput ของ API ได้อย่างมากครับ
-
Dependency Injection System:
FastAPI มีระบบ Dependency Injection ที่ทรงพลังและใช้งานง่าย ทำให้การจัดการ Dependencies ต่าง ๆ เช่น การเชื่อมต่อฐานข้อมูล, การตรวจสอบสิทธิ์ผู้ใช้, หรือการนำ Logic บางส่วนมาใช้ซ้ำ ทำได้สะดวกและเป็นระเบียบครับ
ด้วยข้อดีเหล่านี้ ทำให้ FastAPI เป็นตัวเลือกที่ยอดเยี่ยมสำหรับการสร้าง REST API ที่ต้องการความเร็ว ประสิทธิภาพ และความง่ายในการพัฒนาครับ ไม่ว่าจะเป็น Microservices, Machine Learning API หรือ Web Application Backend ก็สามารถนำ FastAPI ไปประยุกต์ใช้ได้อย่างมีประสิทธิภาพ
2. เตรียมความพร้อมก่อนเริ่มสร้าง API
ก่อนที่เราจะเริ่มเขียนโค้ด FastAPI กันจริง ๆ เรามาเตรียมสภาพแวดล้อมการทำงานให้พร้อมกันก่อนครับ ขั้นตอนเหล่านี้เป็นสิ่งสำคัญที่จะช่วยให้โปรเจกต์ของคุณเป็นระเบียบและปราศจากปัญหาเรื่อง Dependencies ครับ
ติดตั้ง Python และ Pipenv (หรือ Virtualenv/Poetry)
สิ่งแรกที่คุณต้องมีคือ Python ครับ แนะนำให้ใช้ Python เวอร์ชัน 3.7 ขึ้นไป เนื่องจาก FastAPI ใช้คุณสมบัติบางอย่างของ Python เวอร์ชันใหม่ ๆ
ตรวจสอบการติดตั้ง Python
คุณสามารถตรวจสอบว่ามี Python ติดตั้งอยู่แล้วหรือไม่ และเป็นเวอร์ชันใด โดยเปิด Terminal (หรือ Command Prompt) แล้วพิมพ์คำสั่ง:
python3 --version
หรือบางระบบอาจจะเป็น:
python --version
หากยังไม่มี Python หรือต้องการติดตั้งเวอร์ชันใหม่ สามารถดาวน์โหลดได้จากเว็บไซต์ทางการของ Python: https://www.python.org/downloads/ ครับ
ทำไมต้องใช้ Virtual Environment?
การใช้ Virtual Environment เป็นวิธีปฏิบัติที่ดีที่สุดในการพัฒนา Python ครับ เพราะมันช่วยแยก Dependencies ของโปรเจกต์แต่ละอันออกจากกัน ทำให้โปรเจกต์หนึ่งไม่ไปรบกวนโปรเจกต์อื่น ๆ และช่วยให้การจัดการแพ็กเกจเป็นไปอย่างง่ายดาย
ในบทความนี้ เราจะใช้ Pipenv ซึ่งเป็นเครื่องมือที่รวมเอา Pip (ตัวจัดการแพ็กเกจ) และ Virtualenv (ตัวสร้างสภาพแวดล้อมเสมือน) เข้าไว้ด้วยกัน ทำให้การจัดการ Dependencies ง่ายขึ้นมากครับ
ติดตั้ง Pipenv
เปิด Terminal แล้วพิมพ์คำสั่งนี้เพื่อติดตั้ง Pipenv:
pip3 install pipenv
หรืออาจจะเป็น:
pip install pipenv
หากติดตั้งสำเร็จ คุณสามารถตรวจสอบเวอร์ชันได้ด้วย:
pipenv --version
สร้าง Environment และติดตั้ง FastAPI
เมื่อ Pipenv พร้อมแล้ว เรามาเริ่มสร้างโปรเจกต์ FastAPI ของเรากันเลยครับ
1. สร้างโฟลเดอร์สำหรับโปรเจกต์
สร้างโฟลเดอร์เปล่าสำหรับโปรเจกต์ของคุณ (เช่น my_fastapi_app) แล้วเข้าไปในโฟลเดอร์นั้นใน Terminal:
mkdir my_fastapi_app
cd my_fastapi_app
2. สร้าง Virtual Environment ด้วย Pipenv
ในโฟลเดอร์โปรเจกต์ ให้รันคำสั่งนี้เพื่อสร้าง Virtual Environment และเปิด Shell ของ Environment นั้น:
pipenv shell
คำสั่งนี้จะสร้างไฟล์ Pipfile และ Pipfile.lock ซึ่งจะใช้สำหรับจัดการ Dependencies ของโปรเจกต์ และจะพาคุณเข้าสู่ Virtual Environment ที่สร้างขึ้น สังเกตว่าชื่อโฟลเดอร์โปรเจกต์อาจจะปรากฏขึ้นหน้า prompt ของ Terminal ซึ่งแสดงว่าคุณอยู่ใน Virtual Environment แล้วครับ
3. ติดตั้ง FastAPI และ Uvicorn
ภายใน Virtual Environment ที่เปิดอยู่ ให้ติดตั้ง FastAPI และ Uvicorn ครับ
- FastAPI: คือเฟรมเวิร์กหลักของเรา
- Uvicorn: คือ ASGI Server ที่ใช้รันแอปพลิเคชัน FastAPI (ASGI ย่อมาจาก Asynchronous Server Gateway Interface ซึ่งเป็นมาตรฐานใหม่สำหรับ Python Web Server ที่รองรับ Asynchronous)
pipenv install fastapi "uvicorn[standard]"
คำสั่ง pipenv install จะติดตั้งแพ็กเกจที่ระบุ และเพิ่มชื่อแพ็กเกจเหล่านั้นลงในไฟล์ Pipfile โดยอัตโนมัติ ส่วน "uvicorn[standard]" จะติดตั้ง Uvicorn พร้อมกับ Dependencies ที่จำเป็นสำหรับการใช้งานจริงครับ
ตอนนี้คุณก็พร้อมที่จะเริ่มเขียนโค้ด FastAPI แล้วครับ!
3. สร้าง REST API แรกของคุณด้วย FastAPI
เมื่อเตรียมพร้อมแล้ว เรามาสร้าง API ที่ง่ายที่สุดกัน นั่นคือ “Hello World” ครับ นี่คือจุดเริ่มต้นที่จะทำให้คุณเห็นภาพรวมการทำงานของ FastAPI
ไฟล์ main.py ฉบับเริ่มต้น
สร้างไฟล์ชื่อ main.py ในโฟลเดอร์ my_fastapi_app ของคุณ แล้วใส่โค้ดต่อไปนี้:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
มาทำความเข้าใจโค้ดแต่ละส่วนกันครับ:
from fastapi import FastAPI: เรานำเข้าคลาสFastAPIจากไลบรารีfastapiครับapp = FastAPI(): สร้างอินสแตนซ์ของคลาสFastAPIซึ่งเป็นจุดเริ่มต้นของแอปพลิเคชันของเรา@app.get("/"): นี่คือ decorator ที่ใช้กำหนด path operation ครับ ในที่นี้คือเมื่อมีการส่งคำขอ HTTP GET มาที่ root path (/)async def read_root():: คือฟังก์ชันที่ Fast API จะเรียกใช้เมื่อมีคำขอตรงกับ path operation ด้านบน การใช้async defหมายถึงฟังก์ชันนี้เป็น asynchronous function ซึ่ง FastAPI รองรับอย่างเต็มที่ครับreturn {"message": "Hello World"}: ฟังก์ชันจะส่งคืน Python dictionary ซึ่ง FastAPI จะแปลงเป็น JSON response โดยอัตโนมัติ@app.get("/items/{item_id}"): ตัวอย่างที่สองนี้แสดงการใช้ path parameter{item_id}และ query parameterqitem_id: int: นี่คือ Type Hint ที่บอกว่าitem_idควรจะเป็น integer และ FastAPI จะทำการ validation ให้เราโดยอัตโนมัติq: str = None: นี่คือ query parameter ที่เป็น optional (สามารถส่งหรือไม่ส่งก็ได้) ถ้าไม่ส่งมา ค่าเริ่มต้นจะเป็นNone
การรัน FastAPI Server ด้วย Uvicorn
เมื่อมีไฟล์ main.py แล้ว เราสามารถรันแอปพลิเคชันของเราได้ด้วย Uvicorn ครับ อย่าลืมว่าต้องอยู่ใน Virtual Environment ที่เราสร้างไว้ (ถ้าออกมาแล้ว ให้พิมพ์ pipenv shell อีกครั้งครับ)
ใน Terminal ให้พิมพ์คำสั่ง:
uvicorn main:app --reload
คำสั่งนี้มีความหมายดังนี้ครับ:
uvicorn: เรียกใช้ Uvicorn servermain:app: บอก Uvicorn ว่าให้หาอินสแตนซ์ของ FastAPI ที่ชื่อappในไฟล์main.py--reload: เป็นโหมดสำหรับพัฒนา (development mode) เมื่อคุณแก้ไขไฟล์โค้ด Uvicorn จะรีโหลดเซิร์ฟเวอร์ให้อัตโนมัติ ทำให้คุณไม่ต้องมานั่งรันใหม่ทุกครั้งที่เปลี่ยนโค้ดครับ
เมื่อรันคำสั่งนี้ คุณจะเห็นข้อความคล้าย ๆ กับ:
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 [12345]
INFO: Started server process [12347]
INFO: Waiting for application startup.
INFO: Application startup complete.
นี่หมายความว่าเซิร์ฟเวอร์ FastAPI ของคุณกำลังทำงานอยู่ที่ http://127.0.0.1:8000 ครับ
ทดสอบ API ผ่าน Browser และ Swagger UI
ตอนนี้คุณสามารถเปิดเว็บเบราว์เซอร์และทดสอบ API ของคุณได้เลยครับ
-
ทดสอบ Endpoint
/:ไปที่
http://127.0.0.1:8000/คุณควรจะเห็น JSON response:{"message": "Hello World"} -
ทดสอบ Endpoint
/items/{item_id}:ลองไปที่
http://127.0.0.1:8000/items/5คุณจะเห็น:{"item_id": 5, "q": null}และถ้าลองใส่ query parameter เช่น
http://127.0.0.1:8000/items/5?q=somequeryคุณจะเห็น:{"item_id": 5, "q": "somequery"} -
ทดสอบ Swagger UI (Interactive API Documentation):
นี่คือความมหัศจรรย์ของ FastAPI ครับ! ไปที่
http://127.0.0.1:8000/docsคุณจะเห็นหน้าเว็บที่สวยงามและโต้ตอบได้ ซึ่งแสดงเอกสาร API ของคุณทั้งหมด คุณสามารถลองส่งคำขอ (Try it out) ผ่านหน้านั้นได้โดยตรงเลยครับ สะดวกมากสำหรับการทดสอบและทำความเข้าใจ API ของเราเอง หรือนำไปให้เพื่อนร่วมทีมดูครับ
-
ทดสอบ ReDoc (Alternative API Documentation):
อีกหนึ่งทางเลือกสำหรับเอกสาร API คือ ReDoc ซึ่งมีรูปแบบที่แตกต่างออกไปเล็กน้อย สามารถเข้าถึงได้ที่
http://127.0.0.1:8000/redocครับ
ยินดีด้วยครับ! คุณได้สร้างและรัน REST API แรกของคุณด้วย FastAPI ได้สำเร็จแล้วครับ ในส่วนถัดไป เราจะเจาะลึก HTTP Methods และ Path Operations มากขึ้น
4. หัวใจของ REST API: HTTP Methods และ Path Operations
ใน REST API, HTTP Methods (หรือ Verbs) เป็นตัวระบุว่าคุณต้องการทำอะไรกับทรัพยากรที่ระบุโดย URL (Path) ครับ FastAPI ทำให้การกำหนด Path Operations เหล่านี้เป็นเรื่องง่ายและชัดเจน
นี่คือ HTTP Methods หลัก ๆ ที่เราจะใช้บ่อยที่สุดครับ:
GET: ดึงข้อมูล (Retrieve)
ใช้สำหรับร้องขอข้อมูลจากเซิร์ฟเวอร์ โดยไม่มีการเปลี่ยนแปลงข้อมูลบนเซิร์ฟเวอร์ มักใช้เพื่ออ่านข้อมูล
from fastapi import FastAPI
from typing import List, Dict
app = FastAPI()
fake_items_db = [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"}
]
@app.get("/items/")
async def read_all_items(skip: int = 0, limit: int = 10) -> List[Dict]:
return fake_items_db[skip : skip + limit]
@app.get("/items/{item_id}")
async def read_item(item_id: int) -> Dict:
if item_id >= 0 and item_id < len(fake_items_db):
return fake_items_db[item_id]
return {"error": "Item not found"}
ในตัวอย่างนี้ read_all_items จะดึงรายการทั้งหมด โดยมี skip และ limit สำหรับ pagination และ read_item จะดึงข้อมูลเฉพาะรายการโดยใช้ item_id ครับ
POST: สร้างข้อมูลใหม่ (Create)
ใช้สำหรับส่งข้อมูลไปยังเซิร์ฟเวอร์เพื่อสร้างทรัพยากรใหม่
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Dict
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool = None
items_db = []
@app.post("/items/")
async def create_item(item: Item) -> Item:
items_db.append(item)
return item
ที่นี่เราใช้ Item ซึ่งเป็น Pydantic Model เพื่อกำหนดรูปแบบข้อมูลที่คาดว่าจะได้รับใน Request Body ครับ FastAPI จะทำการตรวจสอบและแปลงข้อมูลให้อัตโนมัติ
PUT: อัปเดตข้อมูลทั้งหมด (Update/Replace)
ใช้สำหรับอัปเดตทรัพยากรที่มีอยู่เดิม โดยปกติจะส่งข้อมูลทั้งหมดเพื่อแทนที่ทรัพยากรเดิม
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item) -> Dict:
if item_id < len(items_db):
items_db[item_id] = item
return {"message": f"Item {item_id} updated successfully", "item": item}
return {"error": "Item not found"}
PUT ควรจะ idempotent หมายความว่าการส่งคำขอ PUT ซ้ำ ๆ ด้วยข้อมูลเดียวกัน จะให้ผลลัพธ์เหมือนเดิมครับ
DELETE: ลบข้อมูล (Delete)
ใช้สำหรับลบทรัพยากรที่ระบุ
@app.delete("/items/{item_id}")
async def delete_item(item_id: int) -> Dict:
if item_id < len(items_db):
del items_db[item_id]
return {"message": f"Item {item_id} deleted successfully"}
return {"error": "Item not found"}
PATCH: อัปเดตข้อมูลบางส่วน (Partial Update)
ใช้สำหรับอัปเดตข้อมูลบางส่วนของทรัพยากร ไม่จำเป็นต้องส่งข้อมูลทั้งหมดเหมือน PUT
from typing import Optional
class ItemUpdate(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
is_offer: Optional[bool] = None
@app.patch("/items/{item_id}")
async def patch_item(item_id: int, item_update: ItemUpdate) -> Dict:
if item_id < len(items_db):
existing_item = items_db[item_id]
update_data = item_update.model_dump(exclude_unset=True) # Get only fields that were set
updated_item = existing_item.model_copy(update=update_data) # Create new item with updated data
items_db[item_id] = updated_item
return {"message": f"Item {item_id} partially updated successfully", "item": updated_item}
return {"error": "Item not found"}
สำหรับ PATCH เราสร้าง Pydantic Model ใหม่ ItemUpdate ที่มี Field เป็น Optional เพื่อบอกว่า Field เหล่านี้อาจจะถูกส่งมาหรือไม่ก็ได้ครับ
การเลือกใช้ HTTP Method ที่ถูกต้องตามหลัก RESTful เป็นสิ่งสำคัญที่ทำให้ API ของคุณมีความชัดเจน เข้าใจง่าย และใช้งานได้ตามวัตถุประสงค์ที่ควรจะเป็นครับ
5. การจัดการข้อมูลด้วย Pydantic และ Request Body
หัวใจสำคัญอย่างหนึ่งที่ทำให้ FastAPI โดดเด่นคือการใช้ Pydantic ในการจัดการข้อมูลครับ Pydantic เป็นไลบรารีที่ช่วยในการทำ Data Validation, Serialization และ Documentation ด้วยการใช้ Python Type Hinting
Pydantic Model: กำหนดรูปแบบข้อมูล
เมื่อเราต้องการรับข้อมูลจาก Request Body (เช่น ในคำขอ POST หรือ PUT) เรามักจะต้องการให้ข้อมูลนั้นมีโครงสร้างที่แน่นอน และมีการตรวจสอบความถูกต้อง Pydantic ช่วยให้เราทำสิ่งนี้ได้อย่างง่ายดายโดยการสร้างคลาสที่สืบทอดมาจาก pydantic.BaseModel ครับ
มาดูตัวอย่าง Item Model ที่เราใช้ไปก่อนหน้านี้ครับ:
from pydantic import BaseModel, Field
from typing import Optional
class Item(BaseModel):
name: str = Field(..., example="Awesome Gadget")
description: Optional[str] = Field(
None, example="A very useful device"
)
price: float = Field(..., gt=0, example=12.99) # price must be greater than 0
tax: Optional[float] = Field(None, le=10.0, example=1.3) # tax must be less than or equal to 10.0
tags: list[str] = Field([], example=["electronics", "gadget"])
Type Hinting และ Data Validation
จากตัวอย่างข้างต้น FastAPI ใช้ Pydantic ในการทำสิ่งเหล่านี้ครับ:
- Type Hinting: เรากำหนดประเภทข้อมูลสำหรับแต่ละ Field เช่น
name: str,price: float - Default Values: คุณสามารถกำหนดค่าเริ่มต้นได้โดยตรง เช่น
description: Optional[str] = Noneหมายความว่า Field นี้เป็น Optional และถ้าไม่ส่งมาจะมีค่าเป็นNone - Required Fields: หากไม่มีค่าเริ่มต้น Pydantic จะถือว่า Field นั้นเป็น Required เช่น
name: str - Extended Validation (ด้วย
Field): Pydantic มีFieldclass ที่ช่วยให้เราสามารถกำหนดข้อจำกัดเพิ่มเติมได้ เช่น:gt(greater than): มากกว่าge(greater than or equal): มากกว่าหรือเท่ากับlt(less than): น้อยกว่าle(less than or equal): น้อยกว่าหรือเท่ากับmin_length,max_length(สำหรับ string)regex(สำหรับ string)...: แทน Required Field ที่ไม่มีค่าเริ่มต้น (ใช้เมื่อไม่มีค่า default แต่ต้องการใช้ Field เพิ่มเติม เช่นexample)
- Examples: สามารถเพิ่ม
exampleargument เข้าไปในFieldเพื่อให้ Swagger UI แสดงตัวอย่าง Request Body ที่ชัดเจนขึ้นครับ
เมื่อ Request Body ที่ส่งเข้ามาไม่ตรงตาม Pydantic Model ที่กำหนดไว้ FastAPI จะตอบกลับด้วย HTTP 422 Unprocessable Entity โดยอัตโนมัติ พร้อมข้อความผิดพลาดที่ชัดเจน นี่ช่วยลดเวลาในการเขียนโค้ดตรวจสอบข้อมูลและทำให้ API ของเรามีความแข็งแกร่งมากขึ้นครับ
Nested Models และ List of Models
Pydantic ไม่ได้จำกัดอยู่แค่การสร้าง Model แบบ Flat เท่านั้น แต่ยังสามารถรองรับ Nested Models (Model ซ้อน Model) และ List ของ Models ได้ด้วย ทำให้เราสามารถสร้างโครงสร้างข้อมูลที่ซับซ้อนได้อย่างง่ายดายครับ
from pydantic import BaseModel
from typing import List, Optional
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
class Comment(BaseModel):
text: str
author: User # Nested Model
class Post(BaseModel):
title: str
content: str
author: User # Another Nested Model
comments: List[Comment] = [] # List of Models
@app.post("/posts/")
async def create_post(post: Post):
return post
ในตัวอย่าง Post Model เรามี Field author ที่เป็น User Model และ comments ที่เป็น List ของ Comment Model ครับ FastAPI และ Pydantic จะจัดการการตรวจสอบข้อมูลและแปลงข้อมูลทั้งหมดนี้ให้คุณโดยอัตโนมัติ ซึ่งเป็นความสามารถที่ทรงพลังมากในการจัดการกับโครงสร้างข้อมูลที่ซับซ้อนครับ
6. Query Parameters และ Path Parameters
สองวิธีหลักในการส่งข้อมูลไปยัง API ผ่าน URL คือ Path Parameters และ Query Parameters ครับ FastAPI มีวิธีที่สวยงามและใช้งานง่ายในการจัดการกับทั้งสองแบบ
Path Parameters: ระบุส่วนหนึ่งของ URL
Path Parameters คือส่วนหนึ่งของ URL ที่ใช้ระบุทรัพยากรเฉพาะ เช่น /items/123 โดย 123 คือ item_id
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
จากตัวอย่าง:
{user_id}ใน decorator จะถูกจับคู่กับพารามิเตอร์user_idในฟังก์ชันuser_id: intเป็น Type Hint ที่บอก FastAPI ว่าuser_idควรเป็น integer ถ้าไม่ใช่ FastAPI จะตอบกลับด้วย HTTP 422 โดยอัตโนมัติ
Query Parameters: กรองหรือค้นหาข้อมูล
Query Parameters คือพารามิเตอร์ที่อยู่ต่อท้ายเครื่องหมาย ? ใน URL และคั่นด้วย & เช่น /items?skip=0&limit=10 ใช้สำหรับกรอง, ค้นหา หรือจำกัดข้อมูล
@app.get("/products/")
async def get_products(
name: Optional[str] = None,
min_price: float = 0.0,
max_price: Optional[float] = None,
in_stock: bool = False
):
filters = {
"name": name,
"min_price": min_price,
"max_price": max_price,
"in_stock": in_stock
}
return {"message": "Filtering products", "filters": filters}
จากตัวอย่าง:
- พารามิเตอร์ที่ไม่ได้อยู่ใน Path จะถูกตีความว่าเป็น Query Parameters
name: Optional[str] = None:nameเป็น Optional query parameter และมีค่าเริ่มต้นเป็นNonemin_price: float = 0.0:min_priceเป็น float และมีค่าเริ่มต้นเป็น0.0- FastAPI จะทำการแปลงประเภทข้อมูลและตรวจสอบความถูกต้องให้โดยอัตโนมัติเช่นเคย
Multiple Path และ Query Parameters
คุณสามารถใช้ Path Parameters และ Query Parameters ร่วมกันได้ใน Path Operation เดียวกันครับ
@app.get("/users/{user_id}/items/{item_id}")
async def get_user_item(
user_id: int,
item_id: str,
q: Optional[str] = None,
short: bool = False
):
item = {"item_id": item_id, "owner_id": user_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that belongs to a specific user."}
)
return item
FastAPI จะแยกแยะ Path Parameters (user_id, item_id) และ Query Parameters (q, short) ออกจากกันโดยอัตโนมัติ และทำการแปลงประเภทข้อมูลให้ถูกต้อง ทำให้โค้ดของเราสะอาดและอ่านง่ายครับ
7. การเชื่อมต่อกับฐานข้อมูล (Database Integration)
API ส่วนใหญ่จำเป็นต้องโต้ตอบกับฐานข้อมูลเพื่อจัดเก็บและดึงข้อมูล ในส่วนนี้เราจะมาดูกันว่า FastAPI สามารถเชื่อมต่อกับฐานข้อมูลได้อย่างไรครับ
เลือกฐานข้อมูลที่เหมาะสม
การเลือกฐานข้อมูลขึ้นอยู่กับความต้องการของโปรเจกต์คุณครับ โดยหลัก ๆ จะแบ่งเป็น SQL (Relational) และ NoSQL (Non-Relational)
| คุณสมบัติ | SQL (Relational Databases) | NoSQL (Non-Relational Databases) |
|---|---|---|
| ตัวอย่าง | PostgreSQL, MySQL, SQLite, SQL Server | MongoDB, Cassandra, Redis, DynamoDB |
| โครงสร้างข้อมูล | แบบตาราง (Tables), มี Schema ที่กำหนดตายตัว (Strict Schema) | หลากหลายรูปแบบ (Key-Value, Document, Column-Family, Graph) ไม่มี Schema ที่ตายตัว (Flexible Schema) |
| ความสัมพันธ์ | มีความสัมพันธ์ที่ชัดเจน (Foreign Keys, Joins) | ไม่มีความสัมพันธ์ที่ชัดเจน (หรือจัดการเองในระดับแอปพลิเคชัน) |
| ปรับขนาด (Scaling) | Vertical Scaling (เพิ่มทรัพยากรเครื่องเดียว), Horizontal Scaling ทำได้ยากกว่า (Partitioning) | Horizontal Scaling (กระจายข้อมูลหลายเครื่อง) ทำได้ง่ายกว่า |
| ACID Properties | มักจะรองรับ (Atomicity, Consistency, Isolation, Durability) | บางตัวรองรับ, บางตัวเน้น BASE (Basically Available, Soft state, Eventually consistent) เพื่อประสิทธิภาพ |
| เหมาะสำหรับ | ข้อมูลที่มีโครงสร้างชัดเจน, ความสัมพันธ์ซับซ้อน, ความต้องการ Transaction ที่เข้มงวด | ข้อมูลปริมาณมาก, ข้อมูลที่มีโครงสร้างยืดหยุ่น, Real-time applications, Big Data |
| ความซับซ้อน | ออกแบบ Schema ยากกว่าในตอนแรก, แต่จัดการความสัมพันธ์ง่าย | ออกแบบง่ายกว่าในตอนแรก, แต่จัดการความสัมพันธ์และ Consistency ยากกว่า |
ในบทความนี้ เราจะใช้ SQLite ซึ่งเป็นฐานข้อมูลแบบไฟล์ที่ติดตั้งง่าย และ SQLAlchemy ORM (Object Relational Mapper) ซึ่งเป็นไลบรารีที่ยอดเยี่ยมสำหรับการโต้ตอบกับฐานข้อมูล SQL ด้วย Python ครับ
ตัวอย่าง: SQLite ด้วย SQLAlchemy ORM
เราจะสร้าง API สำหรับจัดการ “ฮีโร่” โดยใช้ SQLAlchemy กับ SQLite ครับ
1. ติดตั้ง SQLAlchemy และ Pydantic-SQLAlchemy
เราจะใช้ SQLAlchemy เป็น ORM และ pydantic-sqlalchemy เพื่อช่วยแปลง SQLAlchemy Models เป็น Pydantic Models ได้ง่ายขึ้นครับ
pipenv install sqlalchemy pydantic-sqlalchemy
2. กำหนด Connection และ Session
สร้างไฟล์ database.py:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" # SQLite database file
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} # Needed for SQLite with FastAPI
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
อธิบายโค้ด:
SQLALCHEMY_DATABASE_URL: กำหนด URL ของฐานข้อมูล ในที่นี้คือไฟล์ SQLite ชื่อsql_app.dbcreate_engine: สร้าง SQLAlchemy Engine ซึ่งเป็นตัวเชื่อมต่อกับฐานข้อมูลconnect_args={"check_same_thread": False}: จำเป็นสำหรับ SQLite เพื่อให้ FastAPI สามารถทำงานแบบ asynchronous ได้sessionmaker: สร้างคลาสสำหรับ Session ซึ่งเป็นตัวจัดการการสื่อสารกับฐานข้อมูล (CRUD operations)Base = declarative_base(): เป็นคลาสพื้นฐานที่เราจะใช้สร้าง SQLAlchemy Models
3. สร้าง SQLAlchemy Models
สร้างไฟล์ models.py เพื่อกำหนดโครงสร้างตารางในฐานข้อมูลครับ
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.orm import relationship
from .database import Base
class Hero(Base):
__tablename__ = "heroes"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
secret_name = Column(String)
age = Column(Integer, default=0)
is_active = Column(Boolean, default=True)
เมื่อรันแอปพลิเคชันครั้งแรก โค้ดนี้จะสร้างตาราง heroes ในไฟล์ sql_app.db โดยอัตโนมัติครับ
4. สร้าง Pydantic Schemas
เรายังคงใช้ Pydantic เพื่อจัดการข้อมูลที่เข้าและออกจาก API ครับ สร้างไฟล์ schemas.py:
from pydantic import BaseModel, Field
from typing import Optional
class HeroBase(BaseModel):
name: str = Field(..., example="Deadpond")
secret_name: str = Field(..., example="Dive Wilson")
age: Optional[int] = Field(None, example=28)
is_active: Optional[bool] = Field(True, example=True)
class HeroCreate(HeroBase):
pass
class HeroUpdate(HeroBase):
name: Optional[str] = None
secret_name: Optional[str] = None
class Hero(HeroBase):
id: int
class Config:
from_attributes = True # Pydantic v2. For v1: orm_mode = True
เราสร้าง Pydantic Models หลายตัวเพื่อวัตถุประสงค์ที่แตกต่างกัน:
HeroBase: ใช้เป็น Base สำหรับ Field ที่พบบ่อยHeroCreate: สำหรับตอนสร้างฮีโร่ใหม่ (รับ Field เหมือนHeroBase)HeroUpdate: สำหรับตอนอัปเดต (ทุก Field เป็น Optional)Hero: สำหรับตอน