ในโลกของการพัฒนาซอฟต์แวร์ยุคใหม่ การเชื่อมต่อระบบต่างๆ เข้าด้วยกันผ่าน Application Programming Interface (API) กลายเป็นหัวใจสำคัญที่ขาดไม่ได้ครับ โดยเฉพาะอย่างยิ่ง REST API ที่เป็นมาตรฐานยอดนิยมที่ช่วยให้แอปพลิเคชันฝั่ง Frontend, Mobile หรือแม้แต่บริการ Backend ต่างๆ สามารถสื่อสารกันได้อย่างราบรื่นและมีประสิทธิภาพ และเมื่อพูดถึงการสร้าง REST API ด้วย Python หนึ่งในเฟรมเวิร์กที่กำลังมาแรงและได้รับความนิยมอย่างก้าวกระโดดในปัจจุบันก็คือ FastAPI นั่นเองครับ
FastAPI ไม่ได้เป็นเพียงเฟรมเวิร์กที่รวดเร็ว (ตามชื่อ) เท่านั้น แต่ยังมาพร้อมกับฟีเจอร์อันทรงพลังมากมาย เช่น การรองรับ Asynchronous Programming (async/await) ที่ทำให้ API ของคุณสามารถจัดการ Request จำนวนมหาศาลได้อย่างมีประสิทธิภาพ, ระบบ Type Hinting ที่ช่วยให้โค้ดของคุณอ่านง่ายและลดข้อผิดพลาด, การตรวจสอบข้อมูลอัตโนมัติด้วย Pydantic และที่สำคัญคือ การสร้างเอกสาร API (Swagger UI และ ReDoc) แบบ Interactive ให้คุณโดยอัตโนมัติ ทำให้การพัฒนาและบำรุงรักษา API เป็นเรื่องง่ายอย่างเหลือเชื่อครับ
บทความนี้จะพาคุณดำดิ่งสู่โลกของการสร้าง REST API ด้วย FastAPI แบบครบวงจร ตั้งแต่การทำความเข้าใจพื้นฐานของ REST API และ FastAPI การเตรียมสภาพแวดล้อม การเขียนโค้ดเพื่อจัดการ Path และ Query Parameters การส่งข้อมูลด้วย Request Body การเชื่อมต่อกับฐานข้อมูล (จำลอง) การจัดการ Error ไปจนถึงการทำ Authentication และแนวคิดการ Deploy เบื้องต้น เพื่อให้คุณสามารถสร้าง API ที่พร้อมใช้งานจริงได้อย่างมั่นใจครับ ไม่ว่าคุณจะเป็นนักพัฒนาที่เริ่มต้นใหม่ หรือมีประสบการณ์อยู่แล้ว แต่อยากเรียนรู้เครื่องมือใหม่ๆ บทความนี้จะเป็นไกด์ที่สมบูรณ์แบบสำหรับคุณอย่างแน่นอนครับ!
สารบัญ
- 1. ทำความรู้จักกับ REST API และ FastAPI คืออะไร?
- 2. เตรียมความพร้อม: เครื่องมือและสภาพแวดล้อม
- 3. เริ่มต้นสร้าง API ง่ายๆ ด้วย FastAPI (Hello World)
- 4. การจัดการ Path Parameters และ Query Parameters
- 5. การส่งข้อมูลด้วย Request Body (POST, PUT) และ Pydantic Models
- 6. การจัดการข้อมูลใน Database (จำลองด้วย List หรือ Dictionary)
- 7. การตรวจสอบความถูกต้องของข้อมูล (Validation) และการจัดการข้อผิดพลาด (Error Handling)
- 8. การทำ Authentication และ Authorization (JWT Basis)
- 9. การจัดการ Dependency Injection ด้วย `Depends`
- 10. การ Deploy FastAPI API
- คำถามที่พบบ่อย (FAQ)
- สรุปและ Call-to-Action
1. ทำความรู้จักกับ REST API และ FastAPI คืออะไร?
ก่อนที่เราจะลงมือเขียนโค้ดสร้าง API จริงๆ เรามาทำความเข้าใจพื้นฐานของ REST API และเหตุผลที่ FastAPI กลายเป็นตัวเลือกที่น่าสนใจกันก่อนนะครับ
1.1 REST API คืออะไร?
REST (Representational State Transfer) ไม่ใช่โปรโตคอล แต่เป็น สถาปัตยกรรม (Architectural Style) สำหรับการออกแบบระบบเครือข่ายที่เน้นความเรียบง่ายและสามารถขยายขนาดได้ โดยมีหลักการสำคัญดังนี้ครับ:
- Client-Server: ระบบถูกแบ่งออกเป็น Client (ส่วนหน้า) และ Server (ส่วนหลัง) ที่แยกจากกันอย่างชัดเจน ทำให้สามารถพัฒนาและบำรุงรักษาได้อย่างอิสระ
- Stateless: แต่ละ Request จาก Client ไปยัง Server จะต้องมีข้อมูลที่จำเป็นทั้งหมดในการประมวลผล โดย Server จะไม่เก็บสถานะใดๆ ของ Client ระหว่าง Request ทำให้ระบบมีความยืดหยุ่นและขยายขนาดได้ง่าย
- Cacheable: Response จาก Server สามารถระบุได้ว่าเป็นข้อมูลที่สามารถ Cache ได้หรือไม่ เพื่อปรับปรุงประสิทธิภาพการทำงาน
- Layered System: Client อาจไม่รู้ว่ากำลังเชื่อมต่อกับ Server โดยตรง หรือผ่าน Layer อื่นๆ เช่น Load Balancer, Proxy
- Uniform Interface: เป็นหลักการที่สำคัญที่สุดของ REST ประกอบด้วย:
- Identification of Resources: ทรัพยากร (Resource) แต่ละอย่างจะต้องมีตัวระบุที่ไม่ซ้ำกัน ซึ่งก็คือ URL (Uniform Resource Locator) นั่นเองครับ เช่น
/users,/products/123 - Manipulation of Resources Through Representations: Client สามารถแก้ไขทรัพยากรได้ด้วยการส่ง Representation ของทรัพยากรนั้นไป เช่น ส่ง JSON Object เพื่อสร้างหรืออัปเดตข้อมูล
- Self-descriptive Messages: แต่ละข้อความที่ส่งระหว่าง Client และ Server จะต้องมีข้อมูลเพียงพอที่จะเข้าใจได้ด้วยตัวมันเอง เช่น HTTP Header, HTTP Method
- Hypermedia as the Engine of Application State (HATEOAS): เป็นการที่ Server ส่งลิงก์ที่เกี่ยวข้องกับทรัพยากรกลับไปให้ Client เพื่อให้ Client สามารถสำรวจและโต้ตอบกับ API ได้อย่างเป็นธรรมชาติ (แม้จะไม่ค่อยมีการนำมาใช้เต็มรูปแบบนักในการใช้งานทั่วไป)
- Identification of Resources: ทรัพยากร (Resource) แต่ละอย่างจะต้องมีตัวระบุที่ไม่ซ้ำกัน ซึ่งก็คือ URL (Uniform Resource Locator) นั่นเองครับ เช่น
โดยทั่วไป REST API จะใช้ HTTP Methods (หรือ Verb) ในการระบุการกระทำกับทรัพยากรต่างๆ ดังนี้ครับ:
GET: ใช้สำหรับดึงข้อมูล (Read)POST: ใช้สำหรับสร้างข้อมูลใหม่ (Create)PUT: ใช้สำหรับอัปเดตข้อมูลทั้งหมดของทรัพยากร (Update/Replace)PATCH: ใช้สำหรับอัปเดตข้อมูลบางส่วนของทรัพยากร (Partial Update)DELETE: ใช้สำหรับลบข้อมูล (Delete)
1.2 FastAPI คืออะไร? ทำไมต้องเลือกใช้?
FastAPI คือ Web Framework สำหรับ Python ที่ใช้ในการสร้าง API โดยเฉพาะครับ มันถูกออกแบบมาให้มีประสิทธิภาพสูง ใช้งานง่าย และมาพร้อมกับฟีเจอร์ที่ช่วยลดเวลาในการพัฒนาได้อย่างมาก
นี่คือเหตุผลสำคัญที่คุณควรพิจารณาเลือกใช้ FastAPI ครับ:
- ประสิทธิภาพสูง: FastAPI สร้างขึ้นบน Starlette (สำหรับ Web parts) และ Pydantic (สำหรับ Data parts) ทำให้ได้ประสิทธิภาพที่เทียบเท่ากับ Node.js และ Go ในบางการทดสอบครับ โดยเฉพาะอย่างยิ่งเมื่อทำงานกับ Asynchronous Code (async/await)
- รองรับ Asynchronous Programming: คุณสามารถเขียนโค้ดที่สามารถจัดการ Request พร้อมกันได้หลายๆ Request โดยไม่ต้องรอให้ Request หนึ่งเสร็จสิ้นก่อน ช่วยให้ API ของคุณรองรับผู้ใช้งานจำนวนมากได้ดีขึ้น
- Type Hinting และ Pydantic: FastAPI ใช้ Python Type Hinting และ Pydantic ในการตรวจสอบความถูกต้องของข้อมูล (Data Validation) และแปลงข้อมูล (Data Serialization/Deserialization) โดยอัตโนมัติ ทำให้โค้ดของคุณปลอดภัย ลดข้อผิดพลาด และอ่านง่ายขึ้นมากครับ
- เอกสาร API อัตโนมัติ: สิ่งที่โดดเด่นที่สุดคือ FastAPI จะสร้างเอกสาร API แบบ Interactive (Swagger UI และ ReDoc) ให้คุณโดยอัตโนมัติจากโค้ดของคุณเลยครับ ไม่ต้องเสียเวลาเขียนเอกสารแยกต่างหาก ช่วยให้นักพัฒนา Frontend และ Mobile สามารถทำความเข้าใจและเรียกใช้งาน API ของคุณได้ง่ายขึ้น
- ลดเวลาในการพัฒนา: ด้วยฟีเจอร์ต่างๆ เช่น Data Validation, Serialization, Interactive Docs และ Dependency Injection ทำให้คุณสามารถโฟกัสกับการเขียน Business Logic แทนที่จะต้องเสียเวลาไปกับการจัดการ boilerplate code
- ใช้งานง่าย: Syntax ของ FastAPI เข้าใจง่ายและเป็นธรรมชาติ ทำให้ผู้เริ่มต้นสามารถเรียนรู้ได้อย่างรวดเร็ว
- Dependency Injection: ระบบ Dependency Injection ใน FastAPI มีประสิทธิภาพและยืดหยุ่น ช่วยให้คุณสามารถจัดการกับ Component ต่างๆ ของ API ได้อย่างเป็นระเบียบและทดสอบง่าย
ลองดูตารางเปรียบเทียบ FastAPI กับเฟรมเวิร์ก Python ยอดนิยมอื่นๆ เพื่อให้เห็นภาพที่ชัดเจนขึ้นนะครับ:
| คุณสมบัติ | FastAPI | Flask | Django REST Framework (DRF) |
|---|---|---|---|
| วัตถุประสงค์หลัก | สร้าง API ประสิทธิภาพสูง | Micro-framework, สร้าง Web App & API ขนาดเล็ก | Full-stack Web framework (Django) + API |
| ประสิทธิภาพ (Async) | ยอดเยี่ยม (Built-in Async/Await) | ต้องใช้ extension/library เพิ่มเติม | รองรับ Async บางส่วน (Django 3.1+) แต่ยังไม่เต็มรูปแบบเท่า |
| Data Validation & Serialization | อัตโนมัติด้วย Pydantic (ยอดเยี่ยม) | ต้องใช้ library ภายนอก (เช่น Marshmallow) | Built-in ด้วย Django Rest Framework Serializers (ดีมาก) |
| เอกสาร API (Docs) | อัตโนมัติ (Swagger UI, ReDoc) | ต้องใช้ library ภายนอก (เช่น Flasgger) | ต้องใช้ library ภายนอก (เช่น drf-spectacular) |
| Learning Curve | ปานกลาง-ง่าย (ถ้าคุ้นเคย Type Hinting) | ง่ายมาก (สำหรับพื้นฐาน) | ปานกลาง-สูง (ต้องเรียนรู้ Django ก่อน) |
| ขนาดของโปรเจกต์ | ทุกขนาด (ตั้งแต่เล็กถึงใหญ่) | เหมาะกับโปรเจกต์ขนาดเล็กถึงกลาง | เหมาะกับโปรเจกต์ขนาดกลางถึงใหญ่ |
| ความนิยม | เติบโตอย่างรวดเร็ว | เป็นที่นิยมอย่างแพร่หลาย | เป็นที่นิยมอย่างแพร่หลาย |
| Ecosystem | กำลังพัฒนา | กว้างขวางมาก | กว้างขวางมาก (ของ Django) |
จะเห็นได้ว่า FastAPI มีจุดเด่นอย่างมากในเรื่องของประสิทธิภาพ เอกสาร API อัตโนมัติ และการจัดการข้อมูล ซึ่งเป็นสิ่งที่สำคัญอย่างยิ่งในการพัฒนา REST API ยุคใหม่ครับ
2. เตรียมความพร้อม: เครื่องมือและสภาพแวดล้อม
ก่อนที่เราจะเริ่มเขียนโค้ด FastAPI เรามาเตรียมเครื่องมือและสภาพแวดล้อมที่จำเป็นกันก่อนนะครับ เพื่อให้การพัฒนาเป็นไปอย่างราบรื่น
2.1 ติดตั้ง Python
คุณต้องมี Python ติดตั้งอยู่ในเครื่องก่อนครับ แนะนำให้ใช้ Python เวอร์ชัน 3.8 ขึ้นไป (FastAPI รองรับตั้งแต่ 3.7 แต่ 3.8+ จะดีกว่าสำหรับ Type Hinting และฟีเจอร์ใหม่ๆ) สามารถดาวน์โหลดได้จากเว็บไซต์ทางการของ Python: https://www.python.org/downloads/
หลังจากติดตั้งแล้ว ลองตรวจสอบเวอร์ชันของ Python ใน Command Line (หรือ Terminal) ด้วยคำสั่ง:
python --version
# หรือบางระบบอาจใช้
python3 --version
คุณควรเห็นผลลัพธ์ประมาณ Python 3.x.x ครับ
2.2 สร้าง Virtual Environment
การสร้าง Virtual Environment เป็นแนวปฏิบัติที่ดีในการพัฒนา Python ครับ มันช่วยแยก Package ต่างๆ ที่ใช้ในแต่ละโปรเจกต์ออกจากกัน ไม่ให้เกิดความขัดแย้งกัน และทำให้โปรเจกต์ของคุณสามารถทำงานได้อย่างอิสระ
1. สร้างโฟลเดอร์สำหรับโปรเจกต์ของคุณ (เช่น fastapi-tutorial):
mkdir fastapi-tutorial
cd fastapi-tutorial
2. สร้าง Virtual Environment (ตั้งชื่อว่า venv หรืออะไรก็ได้ที่คุณชอบ):
python -m venv venv
3. เปิดใช้งาน Virtual Environment:
- บน Windows:
.\venv\Scripts\activate
source venv/bin/activate
หลังจากเปิดใช้งานแล้ว คุณจะเห็นชื่อ (venv) นำหน้า Command Prompt/Terminal ของคุณครับ นั่นหมายความว่าคุณกำลังทำงานอยู่ใน Virtual Environment แล้ว
2.3 ติดตั้ง FastAPI และ Uvicorn
เมื่อ Virtual Environment พร้อมแล้ว ก็ถึงเวลาติดตั้ง Package ที่จำเป็นครับ
1. ติดตั้ง FastAPI และ Uvicorn:
pip install fastapi uvicorn[standard]
fastapi: คือตัวเฟรมเวิร์ก FastAPIuvicorn: คือ ASGI Server ที่ใช้รันแอปพลิเคชัน FastAPI ของเรา (ASGI ย่อมาจาก Asynchronous Server Gateway Interface ซึ่งเป็นมาตรฐานใหม่สำหรับ Python Web Server ที่รองรับ Asynchronous Code)[standard]: เป็นส่วนเสริมของ Uvicorn ที่จะติดตั้งไลบรารีที่จำเป็นอื่นๆ เช่นhttptoolsและwatchgodเพื่อประสิทธิภาพที่ดีขึ้นและความสามารถในการ Reload โค้ดอัตโนมัติเมื่อมีการเปลี่ยนแปลง
ตอนนี้คุณก็พร้อมที่จะเริ่มต้นสร้าง API แรกของคุณแล้วครับ!
3. เริ่มต้นสร้าง API ง่ายๆ ด้วย FastAPI (Hello World)
มาลองสร้าง API ที่ง่ายที่สุดกันก่อนครับ เพื่อให้เห็นภาพรวมการทำงานของ FastAPI
3.1 โครงสร้างไฟล์เบื้องต้น
ภายในโฟลเดอร์ fastapi-tutorial ของเรา ให้สร้างไฟล์ Python ชื่อ main.py ครับ
fastapi-tutorial/
├── venv/
└── main.py
3.2 เขียนโค้ด FastAPI แรก
เปิดไฟล์ main.py แล้วเพิ่มโค้ดต่อไปนี้ครับ
# main.py
from fastapi import FastAPI
# สร้าง Instance ของ FastAPI
app = FastAPI()
# กำหนด Endpoint สำหรับ HTTP GET Request ที่ Path "/"
@app.get("/")
async def read_root():
"""
Endpoint สำหรับหน้าหลัก ส่งคืนข้อความต้อนรับ.
"""
return {"message": "สวัสดีครับ! ยินดีต้อนรับสู่ FastAPI ของเรา"}
# กำหนด Endpoint อีกอันสำหรับทดสอบ
@app.get("/hello")
async def say_hello():
"""
Endpoint สำหรับทักทาย.
"""
return {"message": "Hello from FastAPI!"}
มาทำความเข้าใจโค้ดทีละส่วนนะครับ:
from fastapi import FastAPI: นำเข้าคลาสFastAPIจากไลบรารีfastapiapp = FastAPI(): สร้าง Instance ของแอปพลิเคชัน FastAPI ของเรา โดยทั่วไปจะตั้งชื่อตัวแปรนี้ว่าappครับ@app.get("/"): นี่คือ Decorator ที่บอก FastAPI ว่าฟังก์ชันที่อยู่ถัดไป (read_root) ควรถูกเรียกเมื่อมี HTTP GET Request เข้ามาที่ Path"/"(ซึ่งก็คือ Root Path ของ API)async def read_root():: นี่คือฟังก์ชันที่ถูกเรียกเมื่อ Request ตรงกับ Endpoint ที่กำหนดไว้ คำว่าasync defบ่งบอกว่านี่คือ ฟังก์ชัน Asynchronous ซึ่ง FastAPI ใช้ประโยชน์จากมันได้เต็มที่เพื่อประสิทธิภาพสูงสุดครับreturn {"message": "สวัสดีครับ! ยินดีต้อนรับสู่ FastAPI ของเรา"}: ฟังก์ชันจะส่งคืน Python Dictionary ซึ่ง FastAPI จะแปลงเป็น JSON Response โดยอัตโนมัติ และส่งกลับไปยัง Client ครับ
3.3 รันและทดสอบ API
ใน Command Line/Terminal ที่คุณเปิดใช้งาน Virtual Environment อยู่ ให้รันคำสั่ง Uvicorn ดังนี้ครับ:
uvicorn main:app --reload
มาทำความเข้าใจคำสั่งนี้กันครับ:
uvicorn: คือคำสั่งเรียก Uvicorn Servermain:app: บอก Uvicorn ว่าให้หา Application Instance ชื่อappที่อยู่ในไฟล์main.py(รูปแบบคือ<ไฟล์>:<ตัวแปร>)--reload: เป็น Flag ที่ทำให้ Uvicorn ตรวจจับการเปลี่ยนแปลงในโค้ดของคุณโดยอัตโนมัติ และ Restart Server ให้ใหม่ทุกครั้งที่คุณบันทึกไฟล์ ซึ่งสะดวกมากสำหรับการพัฒนาครับ
คุณจะเห็นข้อความประมาณนี้ใน Terminal:
INFO: Will watch for changes in these directories: ['/path/to/your/fastapi-tutorial']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [xxxxx] using statreload
INFO: Started server process [xxxxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
ตอนนี้ API ของคุณกำลังทำงานอยู่ที่ http://127.0.0.1:8000 แล้วครับ!
เปิด Web Browser ของคุณแล้วไปที่:
http://127.0.0.1:8000/: คุณควรจะเห็น JSON Response{"message": "สวัสดีครับ! ยินดีต้อนรับสู่ FastAPI ของเรา"}http://127.0.0.1:8000/hello: คุณควรจะเห็น JSON Response{"message": "Hello from FastAPI!"}
3.4 ทำความรู้จักกับ Interactive API Docs (Swagger UI/ReDoc)
นี่คือหนึ่งในฟีเจอร์เด่นของ FastAPI ครับ! ในขณะที่ API ของคุณกำลังทำงานอยู่ ลองเปิด Web Browser แล้วไปที่:
http://127.0.0.1:8000/docs: คุณจะเห็นหน้า Swagger UI ที่สวยงามและ Interactive แสดงเอกสารของ API ของคุณโดยอัตโนมัติ คุณสามารถลองส่ง Request จากหน้านี้ได้เลยครับhttp://127.0.0.1:8000/redoc: คุณจะเห็นหน้า ReDoc ซึ่งเป็นอีกรูปแบบหนึ่งของเอกสาร API ที่อ่านง่ายและสะอาดตา
FastAPI สร้างเอกสารเหล่านี้ให้เราโดยอัตโนมัติจาก Type Hinting และ Docstring ที่เราเขียนไว้ในโค้ดครับ ซึ่งช่วยลดภาระในการเขียนเอกสาร API ได้อย่างมหาศาล และทำให้เอกสาร API ของคุณอัปเดตตรงกับโค้ดจริงเสมอครับ
4. การจัดการ Path Parameters และ Query Parameters
ในการสร้าง API ที่ใช้งานได้จริง เราจำเป็นต้องสามารถส่งข้อมูลไปยัง Endpoint ได้ครับ โดยมี 2 วิธีหลักๆ คือ Path Parameters และ Query Parameters
4.1 Path Parameters: ดึงข้อมูลจาก URL
Path Parameters ใช้สำหรับระบุ Resource ที่ต้องการเข้าถึงโดยตรง โดยจะฝังอยู่ใน Path ของ URL ครับ
ตัวอย่างเช่น หากคุณต้องการดึงข้อมูลผู้ใช้ที่มี ID เป็น 123 คุณอาจใช้ URL เช่น /users/123 โดยที่ 123 คือ Path Parameter
มาเพิ่ม Endpoint สำหรับดึงข้อมูล Item ด้วย ID กันครับ:
# main.py (เพิ่มโค้ดต่อจากเดิม)
@app.get("/items/{item_id}")
async def read_item(item_id: int): # ระบุ Type Hint เป็น int
"""
Endpoint สำหรับดึงข้อมูล Item ตาม ID ที่ระบุ.
"""
return {"item_id": item_id, "message": f"นี่คือ Item ID: {item_id} ครับ"}
@app.get("/users/{user_id}")
async def get_user_profile(user_id: str): # Path Parameter เป็น String
"""
Endpoint สำหรับดึงโปรไฟล์ผู้ใช้ตาม ID.
"""
return {"user_id": user_id, "username": f"User_{user_id}"}
คำอธิบาย:
/items/{item_id}: ใน Decorator@app.get()เราใช้เครื่องหมายปีกกา{}เพื่อระบุว่าitem_idเป็น Path Parameter ครับasync def read_item(item_id: int):: ในฟังก์ชัน เราต้องรับitem_idเป็น Argument และที่สำคัญคือ เราสามารถระบุ Type Hint ให้กับ Path Parameter ได้เลยครับ เช่นitem_id: intซึ่ง FastAPI จะใช้ Pydantic ในการตรวจสอบและแปลงข้อมูลให้อัตโนมัติ ถ้าitem_idที่ส่งมาไม่ใช่ Integer มันจะส่งคืน Error422 Unprocessable Entityให้โดยอัตโนมัติครับ!
ลองไปที่ http://127.0.0.1:8000/docs แล้วดูว่า Endpoint ใหม่ปรากฏขึ้นมาหรือไม่ ลองส่ง Request ด้วย ID ต่างๆ ดูครับ เช่น /items/5 หรือ /users/john_doe
4.2 Query Parameters: ส่งข้อมูลเพิ่มเติม
Query Parameters ใช้สำหรับส่งข้อมูลเพิ่มเติมที่ไม่ใช่ส่วนหนึ่งของการระบุ Resource โดยตรงครับ มักใช้สำหรับการกรอง, การจัดเรียง, หรือการแบ่งหน้า (Pagination) โดยจะปรากฏหลังเครื่องหมาย ? ใน URL และคั่นด้วย & เช่น /items?skip=0&limit=10
มาเพิ่ม Endpoint สำหรับดึงรายการ Item พร้อม Query Parameters กันครับ:
# main.py (เพิ่มโค้ดต่อจากเดิม)
@app.get("/items/") # สังเกตว่าไม่มี Path Parameter ที่นี่
async def read_items(skip: int = 0, limit: int = 10):
"""
Endpoint สำหรับดึงรายการ Item พร้อม Query Parameters สำหรับ pagination.
- `skip`: จำนวน Item ที่จะข้ามไป
- `limit`: จำนวน Item ที่จะดึงมาสูงสุด
"""
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
return fake_items_db[skip : skip + limit]
@app.get("/search")
async def search_items(query: str | None = None, price_min: float = 0.0):
"""
Endpoint สำหรับค้นหา Item ด้วย Query String และ Price Filter.
"""
results = []
if query:
results.append({"query": query, "message": "Searching items..."})
if price_min > 0:
results.append({"price_min": price_min, "message": "Filtering by price..."})
if not results:
return {"message": "No search criteria provided."}
return results
คำอธิบาย:
async def read_items(skip: int = 0, limit: int = 10):: Argument ในฟังก์ชันที่ไม่ได้เป็น Path Parameter จะถูกตีความว่าเป็น Query Parameter ครับskip: int = 0: เรากำหนด Type Hint เป็นintและกำหนดค่า Default เป็น0ถ้า Client ไม่ได้ส่ง Query Parameter ชื่อskipมา FastAPI ก็จะใช้ค่า0แทนครับlimit: int = 10: เช่นเดียวกันกับskipquery: str | None = None: นี่คือ Query Parameter ที่เป็น Optional ครับ การระบุ= Noneทำให้มันเป็น Optional และstr | None(หรือOptional[str]ใน Python เวอร์ชันเก่า) บอกว่าค่าจะเป็น String หรือ None ก็ได้
ลองทดสอบที่ http://127.0.0.1:8000/items/?skip=1&limit=1 หรือ http://127.0.0.1:8000/search?query=laptop&price_min=500.0
4.3 Optional Parameters และ Default Values
ดังที่เห็นในตัวอย่างข้างต้น FastAPI ทำให้การจัดการ Optional Parameters และ Default Values เป็นเรื่องง่ายมากๆ ครับ
- Optional Parameter: เพียงแค่กำหนดค่า Default เป็น
Noneเช่นquery: str | None = None - Parameter ที่มีค่า Default: กำหนดค่า Default ได้โดยตรง เช่น
limit: int = 10 - Parameter ที่ต้องระบุ (Required Parameter): ไม่ต้องกำหนดค่า Default เช่น
item_id: intใน Path Parameter หรือparam_name: strใน Query Parameter ที่ไม่มีค่า Default
5. การส่งข้อมูลด้วย Request Body (POST, PUT) และ Pydantic Models
สำหรับ HTTP Methods อย่าง POST และ PUT เรามักจะต้องการส่งข้อมูลที่มีโครงสร้างซับซ้อน เช่น JSON Object ไปยัง API ซึ่งข้อมูลเหล่านี้จะอยู่ในส่วนของ Request Body ครับ และ FastAPI ก็มี Pydantic เป็นตัวช่วยที่ยอดเยี่ยมในการจัดการเรื่องนี้
5.1 ทำความเข้าใจ Request Body
Request Body คือส่วนของข้อมูลที่ถูกส่งไปพร้อมกับ HTTP Request ครับ มักใช้เมื่อ Client ต้องการส่งข้อมูลจำนวนมากหรือมีโครงสร้างที่ซับซ้อนไปยัง Server เพื่อสร้างหรืออัปเดต Resource
ตัวอย่างเช่น เมื่อคุณสร้างบัญชีผู้ใช้ใหม่ คุณจะส่งข้อมูลชื่อ, อีเมล, รหัสผ่าน เป็นต้น ในรูปแบบ JSON ผ่าน Request Body ไปยัง Endpoint /users ด้วย HTTP POST Method
5.2 สร้าง Pydantic Model สำหรับข้อมูล
Pydantic คือไลบรารีที่ช่วยในการทำ Data Validation และ Settings Management โดยใช้ Python Type Hinting ครับ FastAPI ใช้ Pydantic เป็นแกนหลักในการจัดการ Request Body
มาสร้าง Model สำหรับ Item ที่เราจะสร้างหรืออัปเดตกันครับ สร้างไฟล์ใหม่ชื่อ models.py ในโฟลเดอร์โปรเจกต์ของคุณ
fastapi-tutorial/
├── venv/
├── main.py
└── models.py # ไฟล์ใหม่
ในไฟล์ models.py เพิ่มโค้ดดังนี้:
# models.py
from pydantic import BaseModel, Field
from typing import Optional
class Item(BaseModel):
"""
Pydantic Model สำหรับข้อมูล Item.
"""
name: str = Field(..., example="เสื้อยืดสีขาว") # required field, พร้อมตัวอย่าง
description: Optional[str] = Field(None, example="เสื้อยืดคอตตอน 100% สวมใส่สบาย") # optional field
price: float = Field(..., gt=0, example=299.00) # required, ต้องมากกว่า 0
tax: Optional[float] = Field(None, lt=100, example=29.90) # optional, น้อยกว่า 100
class Config:
schema_extra = {
"example": {
"name": "รองเท้าผ้าใบ",
"description": "รองเท้าผ้าใบคุณภาพดี สำหรับทุกกิจกรรม",
"price": 1250.00,
"tax": 125.00,
}
}
class UserIn(BaseModel):
"""
Pydantic Model สำหรับรับข้อมูลผู้ใช้เมื่อสร้าง (เช่น username, password).
"""
username: str = Field(..., min_length=3, max_length=20)
email: str
password: str = Field(..., min_length=6)
class UserOut(BaseModel):
"""
Pydantic Model สำหรับส่งข้อมูลผู้ใช้ออกไป (ไม่ควรส่ง password).
"""
username: str
email: str
คำอธิบาย models.py:
from pydantic import BaseModel, Field: นำเข้าBaseModelซึ่งเป็นคลาสหลักของ Pydantic และFieldสำหรับกำหนด Validation และ Metadata เพิ่มเติมclass Item(BaseModel):: เราสร้างคลาสItemที่สืบทอดมาจากBaseModelเพื่อให้ Pydantic สามารถจัดการได้name: str = Field(..., example="เสื้อยืดสีขาว"):name: str: กำหนดว่าฟิลด์nameต้องเป็น String...: (Ellipsis) หมายความว่าฟิลด์นี้ ต้องระบุ (Required)example="เสื้อยืดสีขาว": เป็น Metadata ที่ FastAPI จะนำไปแสดงในเอกสาร Swagger UI ให้ดูเป็นตัวอย่าง Request Body ครับ
description: Optional[str] = Field(None, example="เสื้อยืดคอตตอน 100% สวมใส่สบาย"):Optional[str]: หมายความว่าฟิลด์นี้เป็น Optional (จะเป็น String หรือNoneก็ได้)None: กำหนดค่า Default เป็นNone
price: float = Field(..., gt=0, example=299.00):gt=0: กำหนด Constraint ว่าค่าpriceต้อง มากกว่า 0 (Greater Than)
class Config: schema_extra = {...}: เป็นการเพิ่มตัวอย่าง JSON Object สำหรับ Model นี้ ซึ่งจะปรากฏใน Swagger UI ทำให้การทดสอบ API ง่ายขึ้นUserInและUserOut: แสดงให้เห็นว่าเราสามารถมี Model ที่แตกต่างกันสำหรับการรับเข้า (เช่นมี password) และส่งออก (ไม่มี password) ได้ครับ
5.3 สร้าง Endpoint แบบ POST
กลับไปที่ไฟล์ main.py และนำเข้า Model ที่เราสร้างไว้ จากนั้นเพิ่ม Endpoint สำหรับสร้าง Item ใหม่ครับ
# main.py (เพิ่มโค้ด)
from fastapi import FastAPI, HTTPException, status
from typing import List, Dict # จะใช้ในส่วนจัดการฐานข้อมูลจำลอง
from models import Item, UserIn, UserOut # นำเข้า Pydantic Models
app = FastAPI()
# ... โค้ดส่วนอื่นๆ ที่มีอยู่แล้ว ...
# ฐานข้อมูลจำลอง
fake_db = {} # ใช้ dictionary เก็บ item โดยมี item_id เป็น key
next_item_id = 1
@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
async def create_item(item: Item):
"""
Endpoint สำหรับสร้าง Item ใหม่.
- รับข้อมูล Item ในรูปแบบ JSON ผ่าน Request Body.
- ทำการตรวจสอบความถูกต้องของข้อมูลโดย Pydantic อัตโนมัติ.
- ส่งคืน Item ที่สร้างสำเร็จพร้อม ID.
"""
global next_item_id # เพื่อแก้ไขค่าตัวแปร next_item_id ที่อยู่นอกฟังก์ชัน
item_id = next_item_id
fake_db[item_id] = item.model_dump() # เก็บข้อมูลในฐานข้อมูลจำลอง (ใช้ model_dump() เพื่อแปลงเป็น dict)
fake_db[item_id]["id"] = item_id # เพิ่ม id ให้กับข้อมูลที่เก็บ
next_item_id += 1
return fake_db[item_id] # ส่งคืน Item ที่สร้างพร้อม ID
@app.post("/users/", response_model=UserOut, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserIn):
"""
Endpoint สำหรับสร้างผู้ใช้ใหม่.
- รับ UserIn model ผ่าน Request Body.
- ส่งคืน UserOut model โดยไม่รวม password.
"""
# ในโลกจริงจะต้องมีการ hashing password ก่อนเก็บลง DB
# และตรวจสอบว่า username/email ซ้ำหรือไม่
print(f"Creating user: {user.username}, email: {user.email}, password: {user.password}")
# จำลองการสร้าง user และเก็บใน DB
fake_users_db = [] # ฐานข้อมูลผู้ใช้จำลอง (ตัวอย่างเท่านั้น)
fake_users_db.append(user.model_dump())
return UserOut(username=user.username, email=user.email) # ส่งคืน UserOut model
คำอธิบาย:
from models import Item, UserIn, UserOut: นำเข้า Pydantic Models@app.post("/items/", ...): เราใช้ Decorator@app.post()สำหรับ HTTP POST Requestresponse_model=Item: นี่คือฟีเจอร์ที่ยอดเยี่ยมของ FastAPI ครับ! มันจะบอก FastAPI ว่า Response ที่ส่งกลับไปจะต้องมีโครงสร้างตามItemModel ซึ่ง FastAPI จะใช้ในการแปลงข้อมูลและสร้างเอกสาร APIstatus_code=status.HTTP_201_CREATED: กำหนด HTTP Status Code ที่จะส่งคืนเมื่อสร้าง Resource สำเร็จ ตามหลัก RESTful ควรเป็น201 Createdครับasync def create_item(item: Item):: ใน Argument ของฟังก์ชัน เราเพียงแค่ระบุ Type Hint เป็นitem: Item(ซึ่งคือ Pydantic Model ของเรา) FastAPI จะจัดการดังนี้:- อ่าน Request Body ที่เข้ามา
- ตรวจสอบว่าเป็น JSON หรือไม่
- แปลง JSON เป็น Python Dictionary
- ตรวจสอบความถูกต้องของข้อมูล (Validation) ตาม Pydantic Model
Item - ถ้าข้อมูลไม่ถูกต้อง จะส่ง Error
422 Unprocessable Entityกลับไปโดยอัตโนมัติ - ถ้าข้อมูลถูกต้อง จะสร้าง Instance ของ
Itemให้คุณพร้อมใช้งานในฟังก์ชัน
item.model_dump(): ใน Pydantic V2+ จะใช้model_dump()แทนdict()เพื่อแปลง Model เป็น Python Dictionary ครับ- สำหรับ
create_userเราใช้UserInในการรับข้อมูลและUserOutในการส่งข้อมูลกลับ เพื่อปกป้องข้อมูล sensitive อย่างรหัสผ่านครับ
ลองไปที่ http://127.0.0.1:8000/docs คุณจะเห็น Endpoint /items/ (POST) และ /users/ (POST) ปรากฏขึ้นมา ลอง “Try it out” และส่ง JSON Body ตามตัวอย่างดูครับ
5.4 สร้าง Endpoint แบบ PUT
HTTP PUT Method ใช้สำหรับอัปเดตข้อมูลทั้งหมดของ Resource ครับ โดยปกติจะส่ง Request Body ที่มีข้อมูลใหม่ทั้งหมดของ Resource นั้นๆ
# main.py (เพิ่มโค้ดต่อจากเดิม)
@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: int, item: Item):
"""
Endpoint สำหรับอัปเดตข้อมูล Item ทั้งหมดตาม ID.
- รับ Item ID ผ่าน Path Parameter.
- รับข้อมูล Item ใหม่ในรูปแบบ JSON ผ่าน Request Body.
- ส่งคืน Item ที่อัปเดตสำเร็จ.
"""
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
fake_db[item_id] = item.model_dump()
fake_db[item_id]["id"] = item_id # อัปเดต ID อีกครั้งเพื่อให้มั่นใจว่ามี
return fake_db[item_id]
ใน Endpoint update_item เราจะรับทั้ง item_id (Path Parameter) เพื่อระบุ Item ที่ต้องการอัปเดต และ item: Item (Request Body) เพื่อรับข้อมูลใหม่ทั้งหมดของ Item นั้นๆ ครับ
ลองไปที่ Swagger UI แล้วลองสร้าง Item ด้วย POST ก่อน จากนั้นนำ item_id ที่ได้ไปใช้กับ PUT Endpoint เพื่ออัปเดตข้อมูลดูครับ
6. การจัดการข้อมูลใน Database (จำลองด้วย List หรือ Dictionary)
ในตัวอย่างข้างต้น เราใช้ fake_db ที่เป็น Python Dictionary ในหน่วยความจำ ซึ่งข้อมูลจะหายไปเมื่อ Server Restart ในการใช้งานจริง เราจะต้องเชื่อมต่อกับฐานข้อมูลจริงๆ เช่น PostgreSQL, MySQL, MongoDB เป็นต้น
ในบทความนี้ เราจะยังคงใช้ In-memory Database (List หรือ Dictionary) เพื่อให้คุณเข้าใจหลักการของ CRUD Operations ได้ง่ายขึ้นครับ โดยไม่เน้นรายละเอียดการเชื่อมต่อกับฐานข้อมูลจริง เพื่อให้บทความไม่ซับซ้อนเกินไป แต่ถ้าคุณต้องการเรียนรู้การเชื่อมต่อฐานข้อมูลจริงๆ สามารถ อ่านเพิ่มเติมที่นี่ ได้ครับ
6.1 การใช้ In-memory Database (ง่ายที่สุด)
เราได้สร้าง fake_db = {} ไว้แล้วใน main.py ซึ่งเป็น Dictionary ที่เก็บข้อมูล Item โดยมี item_id เป็น Key ครับ
# main.py (ส่วนบนสุดของไฟล์)
# ...
from typing import List, Dict, Optional # เพิ่ม Optional
# ฐานข้อมูลจำลอง (ในหน่วยความจำ)
# ใช้ Dict เพื่อให้สามารถค้นหา/อัปเดต/ลบด้วย ID ได้ง่าย
fake_db: Dict[int, Dict] = {} # เก็บข้อมูลในรูปแบบ {item_id: {name: "...", price: ...}}
next_item_id: int = 1 # ตัวนับ ID ถัดไป
6.2 CRUD Operations (Create, Read, Update, Delete)
เราได้เห็น Create (POST) และ Update (PUT) ไปแล้ว ทีนี้มาดู Read และ Delete กันครับ
GET /items (Read all)
Endpoint นี้จะดึงรายการ Item ทั้งหมด หรือบางส่วนตาม Query Parameters
# main.py (เพิ่มโค้ดต่อจากเดิม)
@app.get("/items/", response_model=List[Item])
async def read_all_items(skip: int = 0, limit: int = 10):
"""
Endpoint สำหรับดึงรายการ Item ทั้งหมด หรือบางส่วน.
- `skip`: จำนวน Item ที่จะข้ามไป
- `limit`: จำนวน Item ที่จะดึงมาสูงสุด
"""
# แปลง dictionary values เป็น list แล้วทำการ slice
items_list = list(fake_db.values())
return items_list[skip : skip + limit]
เราใช้ response_model=List[Item] เพื่อบอกว่า Response จะเป็น List ของ Item Model ครับ
GET /items/{item_id} (Read one)
Endpoint นี้จะดึงข้อมูล Item เดียวตาม ID
# main.py (มีอยู่แล้ว แต่เพื่อความชัดเจน)
@app.get("/items/{item_id}", response_model=Item)
async def read_single_item(item_id: int):
"""
Endpoint สำหรับดึงข้อมูล Item เดียวตาม ID.
- ส่งคืน Item ที่พบ หรือ Error 404 หากไม่พบ.
"""
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return fake_db[item_id]
DELETE /items/{item_id} (Delete)
Endpoint นี้จะลบ Item ออกจากฐานข้อมูล
# main.py (เพิ่มโค้ดต่อจากเดิม)
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
"""
Endpoint สำหรับลบ Item ตาม ID.
- ส่งคืน Status 204 No Content หากลบสำเร็จ.
- ส่งคืน Error 404 หากไม่พบ Item.
"""
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
del fake_db[item_id]
return # ไม่ส่งคืน Body สำหรับ 204 No Content
สำหรับ DELETE Operation เมื่อลบสำเร็จ ตามหลัก RESTful มักจะส่งคืน HTTP Status Code 204 No Content ซึ่งหมายถึงการดำเนินการสำเร็จ แต่ไม่มี Content ที่จะส่งคืนใน Response Body ครับ
ตอนนี้คุณมี API ที่รองรับ CRUD Operations พื้นฐานครบถ้วนแล้วครับ!
7. การตรวจสอบความถูกต้องของข้อมูล (Validation) และการจัดการข้อผิดพลาด (Error Handling)
การจัดการข้อมูลที่ไม่ถูกต้องและข้อผิดพลาดเป็นสิ่งสำคัญในการสร้าง API ที่แข็งแกร่งและเชื่อถือได้ FastAPI มีเครื่องมือที่ยอดเยี่ยมในการช่วยเรื่องนี้ครับ
7.1 Pydantic Validation (Built-in)
ดังที่เราได้เห็นไปแล้ว Pydantic ทำการตรวจสอบความถูกต้องของข้อมูล (Validation) โดยอัตโนมัติเมื่อข้อมูลถูกส่งเข้ามาใน Request Body หรือ Path/Query Parameters
ตัวอย่างการ Validation ที่ Pydantic ทำได้:
- Type Checking: ตรวจสอบว่าข้อมูลมี Type ตรงกับที่ระบุไว้หรือไม่ (เช่น
int,str,float) - Required Fields: ตรวจสอบว่าฟิลด์ที่กำหนดเป็น Required ได้รับข้อมูลมาหรือไม่ (ใช้
...ในFieldหรือไม่กำหนดค่า Default) - Numeric Constraints: เช่น
gt(greater than),ge(greater than or equal),lt(less than),le(less than or equal) - String Constraints: เช่น
min_length,max_length,regex - Email Validation: Pydantic สามารถตรวจสอบรูปแบบอีเมลเบื้องต้นได้
- Custom Validators: คุณสามารถเขียน Validation Logic ของคุณเองได้
หากข้อมูลที่ส่งเข้ามาไม่ตรงตาม Pydantic Model หรือ Type Hint ที่กำหนดไว้ FastAPI จะส่งคืน HTTP Status Code 422 Unprocessable Entity พร้อมกับ JSON Response ที่อธิบายข้อผิดพลาดอย่างละเอียดโดยอัตโนมัติ ซึ่งเป็นมาตรฐานของ OpenAPI ครับ
7.2 การส่งคืน HTTP Status Codes ที่เหมาะสม
การใช้ HTTP Status Code ที่ถูกต้องช่วยให้ Client เข้าใจผลลัพธ์ของ Request ได้โดยไม่ต้องอ่าน Response Body เสมอไปครับ
200 OK: Request สำเร็จ (สำหรับ GET, PUT, PATCH)201 Created: Resource ใหม่ถูกสร้างสำเร็จ (สำหรับ POST)204 No Content: Request สำเร็จ แต่ไม่มี Content ที่จะส่งคืน (สำหรับ DELETE)400 Bad Request: Client ส่ง Request ที่ไม่ถูกต้อง (เช่น ข้อมูลไม่สมบูรณ์)401 Unauthorized: Client ไม่ได้ Authenticate (ไม่มี Token หรือ Token ไม่ถูกต้อง)403 Forbidden: Client Authenticate แล้ว แต่ไม่มีสิทธิ์เข้าถึง Resource นั้น404 Not Found: ไม่พบ Resource ที่ร้องขอ422 Unprocessable Entity: Client ส่งข้อมูลที่ไม่ถูกต้องตาม Schema (เช่น Pydantic Validation Failed)500 Internal Server Error: เกิดข้อผิดพลาดบน Server
เราสามารถกำหนด status_code ได้โดยตรงใน Decorator ของ Endpoint ครับ เช่น @app.post("/items/", status_code=status.HTTP_201_CREATED) หรือใช้ raise HTTPException เพื่อส่ง Status Code ที่ต้องการใน Logic ของเรา
7.3 การจัดการ HTTPException
เมื่อเกิดข้อผิดพลาดที่ต้องส่งคืน HTTP Status Code ที่ไม่ใช่ 200 series คุณสามารถใช้ HTTPException จาก FastAPI ได้ครับ
# main.py (ตัวอย่างเพิ่มเติม)
from fastapi import FastAPI, HTTPException, status
# ...
@app.get("/items/{item_id}", response_model=Item)
async def read_single_item(item_id: int):
"""
Endpoint สำหรับดึงข้อมูล Item เดียวตาม ID.
- ส่งคืน Item ที่พบ หรือ Error 404 หากไม่พบ.
"""
if item_id not in fake_db:
# หากไม่พบ Item ให้ raise HTTPException
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found")
return fake_db[item_id]
@app.post("/login")
async def login_user(username: str, password: str):
"""
ตัวอย่างการตรวจสอบ Login แบบง่ายๆ (ยังไม่มี Authentication จริงจัง).
"""
if username != "admin" or password != "password123":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"}, # Header สำหรับบอก Client ว่าต้องใช้ Bearer Token
)
return {"message": "Login successful!"}
เมื่อ HTTPException ถูก Raise ขึ้นมา FastAPI จะจัดการแปลงเป็น JSON Response ที่มี status_code และ detail ที่ระบุให้โดยอัตโนมัติ ซึ่งเป็นรูปแบบมาตรฐานที่ Client สามารถเข้าใจได้ครับ
8. การทำ Authentication และ Authorization (JWT Basis)
API ส่วนใหญ่จำเป็นต้องมีระบบ Authentication (การยืนยันตัวตนว่าใครคือผู้ใช้งาน) และ Authorization (การตรวจสอบว่าผู้ใช้งานมีสิทธิ์ทำอะไรได้บ้าง) บทความนี้จะแนะนำแนวคิดพื้นฐานของการทำ Token-based Authentication โดยใช้ JWT (JSON Web Tokens) ครับ
8.1 แนวคิดพื้นฐาน: Token-based Authentication
แทนที่จะใช้ Session หรือ Cookie แบบดั้งเดิม Token-based Authentication จะทำงานดังนี้:
- ผู้ใช้ส่ง Username/Password ไปที่ API
/login - Server ตรวจสอบ Username/Password ถ้าถูกต้อง จะสร้าง JWT Token ขึ้นมา
- Server ส่ง JWT Token กลับไปยัง Client
- Client เก็บ Token ไว้ (เช่นใน Local Storage)
- เมื่อ Client ต้องการเข้าถึง Endpoint ที่ต้องการการยืนยันตัวตน Client จะแนบ JWT Token ไปกับทุก Request ใน HTTP Header
Authorization: Bearer <your_jwt_token> - Server ตรวจสอบความถูกต้องของ JWT Token ในแต่ละ Request ถ้า Token ถูกต้องและยังไม่หมดอายุ Server จะอนุญาตให้เข้าถึง Resource นั้นๆ
JWT เป็นชุดข้อมูลที่เข้ารหัสและลงชื่อด้วย Secret Key ทำให้มั่นใจได้ว่าข้อมูลใน Token ไม่ถูกแก้ไข และสามารถตรวจสอบความถูกต้องได้โดย Server โดยไม่ต้อง query ฐานข้อมูลทุกครั้งครับ
8.2 ติดตั้งไลบรารีที่จำเป็น
เราจะใช้ไลบรารี python-jose สำหรับการเข้ารหัส/ถอดรหัส JWT และ passlib สำหรับการ Hash รหัสผ่าน (ซึ่งสำคัญมากในการเก็บรหัสผ่านในฐานข้อมูล) รวมถึง bcrypt ซึ่งเป็น Backend ที่ Passlib ใช้
pip install python-jose[cryptography] passlib[bcrypt]
8.3 สร้าง Endpoint สำหรับ Login (Generate Token)
เราจะสร้าง Endpoint /token ที่รับ Username และ Password แล้วส่งคืน JWT Token ครับ
เพิ่มโค้ดใน main.py:
# main.py (เพิ่มส่วนของ Authentication)
from fastapi import FastAPI, HTTPException, status, Depends
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm # สำหรับ Login/Auth
from typing import List, Dict, Optional
from models import Item, UserIn, UserOut
from jose import JWTError, jwt # สำหรับ JWT
from datetime import datetime, timedelta # สำหรับเวลาของ Token
from passlib.context import CryptContext # สำหรับ Hash รหัสผ่าน
# ... โค้ดส่วนอื่นๆ ...
# --- Configuration สำหรับ JWT ---
SECRET_KEY = "your-secret-key" # ควรเก็บใน Environment Variable ไม่ใช่ในโค้ดจริง
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# สำหรับ Hash รหัสผ่าน
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# สำหรับ OAuth2 Password Flow (FastAPI Docs)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# --- ฟังก์ชันช่วยเกี่ยวกับ Password และ JWT ---
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
# --- ฐานข้อมูลผู้ใช้จำลอง (ในโลกจริงจะมาจาก DB) ---
fake_users_db = {
"john_doe": {
"username": "john_doe",
"hashed_password": get_password_hash("password123"), # Hash รหัสผ่าน
"email": "[email protected]",
},
"admin": {
"username": "admin",
"hashed_password": get_password_hash("adminpass"),
"email": "[email protected]",
}
}
# --- Endpoint สำหรับ Login และสร้าง Token ---
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
"""
Endpoint สำหรับ Login และรับ Access Token.
- รับ username และ password ผ่าน form data.
- ตรวจสอบความถูกต้องของข้อมูลและส่งคืน JWT Access Token.
"""
user_data = fake_users_db.get(form_data.username)
if not user_data or not verify_password(form_data.password, user_data["hashed_password"]):
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_data["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
คำอธิบาย:
SECRET_KEY: คีย์ลับที่ใช้ในการเข้ารหัส/ถอดรหัส JWT สำคัญมาก ห้ามเผยแพร่และควรเก็บใน Environment Variablepwd_context: ใช้สำหรับ Hash รหัสผ่านOAuth2PasswordBearer: เป็น Dependency ที่ FastAPI มีให้สำหรับจัดการ OAuth2 Password Flow ซึ่งจะตรวจสอบ HeaderAuthorization: Bearerให้เราโดยอัตโนมัติlogin_for_access_token: รับform_dataจากOAuth2PasswordRequestForm(ซึ่งเป็น Dependency ที่ FastAPI จัดการให้)- ฟังก์ชัน
create_access_tokenจะสร้าง JWT Token ที่มีข้อมูลsub(subject) เป็น username และมีวันหมดอายุ
8.4 การปกป้อง Endpoint ด้วย `Depends`
เมื่อผู้ใช้ได้ Token มาแล้ว เราจะใช้ Dependency Injection ของ FastAPI เพื่อตรวจสอบ Token ในทุก Request ที่ต้องการการยืนยันตัวตนครับ
# main.py (เพิ่มส่วนของ Authentication)
# ... โค้ดส่วนอื่นๆ ...
# --- Dependency สำหรับดึงผู้ใช้ปัจจุบัน ---
def get_current_user(token: str = Depends(oauth2_scheme)):
"""
Dependency สำหรับตรวจสอบ Token และดึงข้อมูลผู้ใช้ปัจจุบัน.
- หาก Token ไม่ถูกต้อง จะ raise HTTPException 401.
- ส่งคืนข้อมูลผู้ใช้ที่ตรวจสอบแล้ว.
"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = fake_users_db.get(username) # ดึงข้อมูลผู้ใช้จากฐานข้อมูลจำลอง
if user is None:
raise credentials_exception
return user # คืนข้อมูลผู้ใช้ที่ Authenticate แล้ว
# --- Endpoint ที่ต้องการ Authentication ---
@app.get("/users/me/", response_model=UserOut)
async def read_users_me(current_user: dict = Depends(get_current_user)):
"""
Endpoint สำหรับดึงข้อมูลโปรไฟล์ผู้ใช้ปัจจุบัน.
- ต้องมี JWT Token ใน Header `Authorization: Bearer <token>`.
"""
# current_user คือข้อมูลผู้ใช้ที่ผ่านการ Authenticate แล้ว
return UserOut(username=current_user["username"], email=current_user["email"])
@app.get("/protected-item/{item_id}", response_model=Item)
async def read_protected_item(item_id: int, current_user: dict = Depends(get_current_user)):
"""
Endpoint สำหรับดึง Item ที่ถูกป้องกัน.
- ต้องมี JWT Token และผู้ใช้ที่เข้าสู่ระบบเท่านั้นจึงจะเข้าถึงได้.
"""
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
# สามารถเพิ่ม Logic สำหรับ Authorization ได้ที่นี่ เช่น ตรวจสอบว่าผู้ใช้มีสิทธิ์ดู Item นี้หรือไม่
# if current_user["username"] != fake_db[item_id]["owner"]:
# raise HTTPException(status_code=403, detail="Not authorized to view this item")
return fake_db[item_id]
คำอธิบาย:
get_current_user: นี่คือ Dependency Function ที่รับ Token จาก HeaderAuthorizationโดยใช้oauth2_scheme, ถอดรหัส Token, ตรวจสอบความถูกต้อง, และดึงข้อมูลผู้ใช้current_user: dict = Depends(get_current_user): ใน Endpoint ที่ต้องการการยืนยันตัวตน เราเพียงแค่เพิ่ม Argument นี้ FastAPI จะเรียกget_current_userก่อนที่จะรัน Logic ใน Endpoint ของเรา ถ้าget_current_userRaiseHTTPExceptionEndpoint จะไม่ถูกเรียกครับ
ลองไปที่ Swagger UI อีกครั้ง คุณจะเห็นปุ่ม “Authorize” ด้านบนขวา คลิกแล้วใส่ Token ที่