
สวัสดีครับ! ยินดีต้อนรับสู่โลกของการสร้างสรรค์ API ที่ทรงพลังและมีประสิทธิภาพ ในยุคดิจิทัลปัจจุบัน การเชื่อมต่อระหว่างแอปพลิเคชันต่าง ๆ ผ่าน API (Application Programming Interface) ได้กลายเป็นหัวใจสำคัญที่ขับเคลื่อนนวัตกรรม ไม่ว่าจะเป็นแอปพลิเคชันมือถือ เว็บไซต์ หรือระบบหลังบ้าน ทุกวันนี้ล้วนแต่พึ่งพา API ในการแลกเปลี่ยนข้อมูล และเมื่อพูดถึงการสร้าง REST API ด้วยภาษา Python หนึ่งในเฟรมเวิร์กที่มาแรงและได้รับการยอมรับอย่างรวดเร็วคือ FastAPI ครับ
FastAPI ไม่ได้เป็นเพียงแค่เฟรมเวิร์กสร้างเว็บธรรมดา แต่ถูกออกแบบมาเพื่อสร้าง API โดยเฉพาะ ด้วยคุณสมบัติเด่น ๆ มากมาย เช่น ประสิทธิภาพที่เหนือกว่า ความเร็วในการพัฒนาที่ยอดเยี่ยม การตรวจสอบข้อมูลอัตโนมัติ และเอกสาร API แบบอินเทอร์แอคทีฟที่สร้างให้โดยอัตโนมัติ ทำให้ FastAPI กลายเป็นตัวเลือกอันดับต้น ๆ สำหรับนักพัฒนา Python ที่ต้องการสร้าง REST API ที่แข็งแกร่งและบำรุงรักษาง่าย
บทความนี้จะพาคุณดำดิ่งสู่โลกของ FastAPI ตั้งแต่การทำความเข้าใจพื้นฐานของ REST API ไปจนถึงการสร้าง API ที่ซับซ้อนขึ้นพร้อมกับการเชื่อมต่อฐานข้อมูล การจัดการโมเดลข้อมูล การตรวจสอบสิทธิ์ (Authentication) และเคล็ดลับต่าง ๆ ที่จะช่วยให้คุณสร้าง REST API ด้วย FastAPI ได้อย่างครบวงจรและมีประสิทธิภาพ เราจะเริ่มต้นจากศูนย์และก้าวไปพร้อมกันทีละขั้นตอน เพื่อให้คุณสามารถนำความรู้ที่ได้ไปประยุกต์ใช้ในการพัฒนาโปรเจกต์ของคุณเองได้อย่างมั่นใจครับ
สารบัญ
- ทำความรู้จักกับ REST API คืออะไร?
- ทำไมต้องเลือก FastAPI ในการสร้าง API?
- เตรียมความพร้อมก่อนเริ่มต้น
- ติดตั้งและตั้งค่าสภาพแวดล้อม
- สร้าง Virtual Environment
- ติดตั้ง FastAPI และ Uvicorn
- FastAPI “Hello World” แรกของคุณ
- รัน FastAPI แอปพลิเคชัน
- สำรวจเอกสาร API อัตโนมัติ (Swagger UI และ ReDoc)
- เจาะลึกแนวคิดหลักของ FastAPI
- Path Operations และ HTTP Methods
- Path Parameters
- Query Parameters
- Request Body และ Pydantic Models
- Response Model: กำหนดรูปแบบการตอบกลับ
- Dependency Injection: การพึ่งพิงที่ยืดหยุ่น
- การจัดการข้อผิดพลาด (Error Handling)
- การเชื่อมต่อฐานข้อมูล (SQLite & SQLAlchemy)
- ตั้งค่า SQLAlchemy และ SQLite
- สร้างโมเดลฐานข้อมูลด้วย SQLAlchemy
- การจัดการ Database Session
- สร้าง CRUD Operations สำหรับ Item
- การตรวจสอบสิทธิ์และการอนุญาต (Authentication & Authorization)
- ข้อควรพิจารณาในการ Deploy
- หัวข้อขั้นสูงเพิ่มเติม
- คำถามที่พบบ่อย (FAQ)
- สรุปและ Call to Action
ทำความรู้จักกับ REST API คืออะไร?
ก่อนที่เราจะเริ่มลงมือเขียนโค้ด มาทำความเข้าใจกันก่อนว่า REST API คืออะไร และทำไมมันถึงเป็นส่วนสำคัญในการพัฒนาซอฟต์แวร์ในปัจจุบันครับ
REST ย่อมาจาก Representational State Transfer ซึ่งเป็นสถาปัตยกรรม (architectural style) สำหรับการออกแบบระบบเครือข่าย โดยเฉพาะอย่างยิ่งสำหรับเว็บเซอร์วิส ที่ถูกคิดค้นโดย Roy Fielding ในปี 2000 REST ไม่ใช่โปรโตคอล แต่เป็นชุดของหลักการและข้อจำกัดที่เมื่อนำมาใช้แล้ว จะทำให้ระบบสามารถสื่อสารกันได้ง่ายขึ้น มีประสิทธิภาพ และปรับขยายได้ดีครับ
เมื่อเราพูดถึง REST API เรากำลังพูดถึง API ที่เป็นไปตามหลักการของ REST โดยใช้โปรโตคอล HTTP ในการสื่อสารระหว่างไคลเอนต์ (เช่น เว็บเบราว์เซอร์, แอปมือถือ) และเซิร์ฟเวอร์
หลักการสำคัญของ REST
REST API มีหลักการสำคัญ 6 ประการ ซึ่งบางครั้งเรียกว่า “Constraints” ดังนี้ครับ:
- Client-Server (ไคลเอนต์-เซิร์ฟเวอร์): การแยกความรับผิดชอบระหว่างไคลเอนต์ (ผู้ร้องขอ) และเซิร์ฟเวอร์ (ผู้ให้บริการ) อย่างชัดเจน ทำให้แต่ละส่วนสามารถพัฒนาและปรับปรุงได้โดยอิสระ ไคลเอนต์ไม่จำเป็นต้องรู้ว่าเซิร์ฟเวอร์ทำงานอย่างไร และเซิร์ฟเวอร์ก็ไม่จำเป็นต้องรู้ว่าไคลเอนต์แสดงผลข้อมูลอย่างไรครับ
- Stateless (ไร้สถานะ): แต่ละคำขอจากไคลเอนต์ไปยังเซิร์ฟเวอร์จะต้องมีข้อมูลที่จำเป็นทั้งหมดในการประมวลผลคำขอนั้น ๆ เซิร์ฟเวอร์จะไม่เก็บข้อมูลสถานะของไคลเอนต์ระหว่างคำขอ ทำให้ระบบมีความยืดหยุ่นและปรับขยายได้ง่ายขึ้น เพราะเซิร์ฟเวอร์ไม่จำเป็นต้องจำคำขอก่อนหน้าของไคลเอนต์แต่ละรายครับ
- Cacheable (สามารถแคชได้): การตอบกลับจากเซิร์ฟเวอร์ควรระบุได้ว่าสามารถแคชข้อมูลนั้นได้หรือไม่ เพื่อลดภาระของเซิร์ฟเวอร์และปรับปรุงประสิทธิภาพการทำงานของไคลเอนต์
- Layered System (ระบบเป็นชั้น): ไคลเอนต์ไม่จำเป็นต้องเชื่อมต่อกับเซิร์ฟเวอร์ปลายทางโดยตรง อาจมี Middleware, Load Balancer หรือ Proxy Server อยู่ระหว่างกลาง ซึ่งแต่ละชั้นจะไม่ส่งผลกระทบต่อการทำงานของไคลเอนต์ครับ
- Uniform Interface (ส่วนต่อประสานที่เป็นหนึ่งเดียว): นี่คือหลักการที่สำคัญที่สุดของ REST API ซึ่งรวมถึง:
- Identification of resources: ทรัพยากร (Resource) แต่ละอย่างจะต้องมี URI (Uniform Resource Identifier) ที่ไม่ซ้ำกัน เช่น
/users,/products/123 - Manipulation of resources through representations: ไคลเอนต์โต้ตอบกับทรัพยากรโดยการส่งและรับ representations ของทรัพยากรนั้น ๆ เช่น ไฟล์ JSON หรือ XML
- Self-descriptive messages: ข้อความที่แลกเปลี่ยนกันควรมีข้อมูลเพียงพอที่จะอธิบายวิธีการประมวลผลข้อความนั้น ๆ
- Hypermedia as the Engine of Application State (HATEOAS): การตอบกลับจาก API ควรมีลิงก์ (hyperlinks) ที่บอกถึงการกระทำถัดไปที่ไคลเอนต์สามารถทำได้ เพื่อนำทางไปยังทรัพยากรอื่น ๆ
- Identification of resources: ทรัพยากร (Resource) แต่ละอย่างจะต้องมี URI (Uniform Resource Identifier) ที่ไม่ซ้ำกัน เช่น
- Code-On-Demand (โค้ดตามความต้องการ – ตัวเลือก): เซิร์ฟเวอร์สามารถขยายฟังก์ชันการทำงานของไคลเอนต์ได้โดยการส่งโค้ดที่รันได้ เช่น JavaScript (เป็นหลักการที่ไม่ค่อยได้ใช้ในการสร้าง REST API ทั่วไปครับ)
HTTP Methods ที่ใช้บ่อย
REST API ใช้ HTTP Methods (หรือ Verbs) เพื่อระบุประเภทของการดำเนินการที่ต้องการทำกับทรัพยากรบนเซิร์ฟเวอร์ครับ
- GET: ใช้สำหรับ อ่าน ข้อมูลจากเซิร์ฟเวอร์ เช่น ขอรายการสินค้าทั้งหมด หรือข้อมูลสินค้าชิ้นเดียว การเรียก GET ควรเป็น idempotent (ไม่ทำให้เกิดการเปลี่ยนแปลงบนเซิร์ฟเวอร์) และปลอดภัย (ไม่มีผลข้างเคียง)
- POST: ใช้สำหรับ สร้าง ทรัพยากรใหม่บนเซิร์ฟเวอร์ เช่น สร้างผู้ใช้ใหม่ หรือเพิ่มสินค้าใหม่ในฐานข้อมูล
- PUT: ใช้สำหรับ อัปเดต ทรัพยากรที่มีอยู่ทั้งหมด หรือ สร้าง ทรัพยากรใหม่หากยังไม่มี โดยปกติจะส่งข้อมูลทั้งหมดของทรัพยากรนั้น ๆ เพื่อแทนที่ข้อมูลเก่า
- PATCH: ใช้สำหรับ อัปเดต ทรัพยากรที่มีอยู่บางส่วน เช่น อัปเดตเฉพาะชื่อผู้ใช้ ไม่ใช่อัปเดตข้อมูลผู้ใช้ทั้งหมด
- DELETE: ใช้สำหรับ ลบ ทรัพยากรออกจากเซิร์ฟเวอร์
HTTP Status Codes
เมื่อไคลเอนต์ส่งคำขอไปยังเซิร์ฟเวอร์ เซิร์ฟเวอร์จะตอบกลับด้วย HTTP Status Code เพื่อระบุสถานะของคำขอ ว่าสำเร็จหรือไม่ มีปัญหาอะไรเกิดขึ้น
- 2xx – Success:
200 OK: คำขอสำเร็จ201 Created: ทรัพยากรใหม่ถูกสร้างขึ้นสำเร็จ (มักใช้กับ POST)204 No Content: คำขอสำเร็จแต่ไม่มีการตอบกลับเนื้อหา (มักใช้กับ DELETE)
- 4xx – Client Error:
400 Bad Request: คำขอไม่ถูกต้อง401 Unauthorized: ไม่ได้รับอนุญาต (ต้องยืนยันตัวตน)403 Forbidden: ไม่มีสิทธิ์เข้าถึง (ยืนยันตัวตนแล้วแต่ไม่มีสิทธิ์)404 Not Found: ไม่พบทรัพยากร405 Method Not Allowed: ไม่รองรับ HTTP Method ที่ใช้409 Conflict: เกิดความขัดแย้ง (เช่น พยายามสร้างทรัพยากรที่มีอยู่แล้ว)422 Unprocessable Entity: คำขอถูกต้องแต่ข้อมูลไม่สามารถประมวลผลได้ (มักใช้เมื่อ Pydantic validation ล้มเหลวใน FastAPI)
- 5xx – Server Error:
500 Internal Server Error: ข้อผิดพลาดภายในเซิร์ฟเวอร์503 Service Unavailable: เซิร์ฟเวอร์ไม่พร้อมให้บริการชั่วคราว
JSON: รูปแบบข้อมูลยอดนิยม
ใน REST API ส่วนใหญ่ ข้อมูลจะถูกแลกเปลี่ยนในรูปแบบ JSON (JavaScript Object Notation) ซึ่งเป็นรูปแบบข้อมูลที่อ่านง่ายสำหรับทั้งมนุษย์และเครื่องจักร มีโครงสร้างที่ชัดเจนและรองรับข้อมูลประเภทต่าง ๆ ไม่ว่าจะเป็น string, number, boolean, array, และ object จึงเป็นตัวเลือกยอดนิยมสำหรับการสื่อสารระหว่าง API ครับ
ทำไมต้องเลือก FastAPI ในการสร้าง API?
ในบรรดาเฟรมเวิร์ก Python สำหรับการสร้างเว็บและ API นั้น FastAPI โดดเด่นขึ้นมาอย่างรวดเร็วในช่วงไม่กี่ปีที่ผ่านมา ด้วยคุณสมบัติที่น่าประทับใจและความสามารถที่เหนือกว่าหลาย ๆ เฟรมเวิร์กครับ
ข้อดีของ FastAPI
- ประสิทธิภาพสูง: FastAPI สร้างขึ้นบน Starlette (สำหรับเว็บ) และ Pydantic (สำหรับการตรวจสอบข้อมูล) ซึ่งเป็นไลบรารีที่รวดเร็วและมีประสิทธิภาพสูงอยู่แล้ว ทำให้ FastAPI เป็นหนึ่งในเฟรมเวิร์ก Python ที่เร็วที่สุด สามารถเทียบเคียงกับ Node.js และ Go ได้เลยครับ
- ความเร็วในการพัฒนาที่ยอดเยี่ยม: ด้วยการใช้ Python Type Hints ร่วมกับ Pydantic ทำให้ FastAPI สามารถตรวจสอบข้อมูล (Data Validation) และแปลงข้อมูล (Serialization/Deserialization) ได้โดยอัตโนมัติ ช่วยลด boilerplate code และเพิ่มความเร็วในการเขียนโค้ดได้อย่างมหาศาลครับ
- เอกสาร API อัตโนมัติ: นี่คือหนึ่งในคุณสมบัติที่โดดเด่นที่สุด! FastAPI สร้างเอกสาร API แบบอินเทอร์แอคทีฟ (Swagger UI และ ReDoc) ให้โดยอัตโนมัติจากโค้ดของคุณ ทำให้การทดสอบและทำความเข้าใจ API ง่ายขึ้นมากสำหรับทั้งนักพัฒนา backend และ frontend ครับ
- การตรวจสอบข้อมูลและการแปลงข้อมูล (Data Validation & Serialization): Pydantic ช่วยให้คุณสามารถกำหนดโครงสร้างข้อมูลที่คาดหวังได้อย่างชัดเจน ทำให้มั่นใจได้ว่าข้อมูลที่เข้ามาและออกไปจาก API ของคุณเป็นไปตามรูปแบบที่ถูกต้อง และยังสามารถแปลงข้อมูลให้เป็น JSON หรือจาก JSON ได้อย่างราบรื่นครับ
- รองรับ Asynchronous (
async/await): FastAPI รองรับการทำงานแบบ Asynchronous I/O อย่างเต็มรูปแบบ ทำให้ API ของคุณสามารถจัดการคำขอพร้อมกันจำนวนมากได้อย่างมีประสิทธิภาพ เหมาะสำหรับแอปพลิเคชันที่ต้องการ Scale สูง ๆ ครับ - Dependency Injection System: ระบบการจัดการ Dependency ที่ทรงพลังและใช้งานง่าย ทำให้โค้ดของคุณเป็นระเบียบ ทดสอบได้ง่าย และบำรุงรักษาได้สะดวกขึ้น
- ความเข้ากันได้กับมาตรฐาน Python: FastAPI ใช้ Python Type Hints ซึ่งเป็นมาตรฐานของ Python อยู่แล้ว ทำให้โค้ดของคุณอ่านง่าย ทำความเข้าใจง่าย และใช้ประโยชน์จากเครื่องมือต่าง ๆ ใน ecosystem ของ Python ได้อย่างเต็มที่ครับ
FastAPI เปรียบเทียบกับ Flask และ Django
เพื่อให้เห็นภาพชัดเจนขึ้น มาดูกันว่า FastAPI แตกต่างจากเฟรมเวิร์ก Python ยอดนิยมอื่น ๆ อย่าง Flask และ Django อย่างไรครับ
| คุณสมบัติ | FastAPI | Flask | Django |
|---|---|---|---|
| ประเภทของเฟรมเวิร์ก | Microframework (แต่มีฟีเจอร์ครบครันสำหรับ API) | Microframework | Full-stack Web Framework |
| เป้าหมายหลัก | สร้าง REST API ที่รวดเร็วและมีประสิทธิภาพ | สร้างเว็บแอปพลิเคชันขนาดเล็ก-กลาง, API | สร้างเว็บแอปพลิเคชันขนาดใหญ่, ซับซ้อน (มี ORM, Admin Panel, Template Engine ในตัว) |
| ประสิทธิภาพ | สูงมาก (เทียบเท่า Node.js, Go) ด้วย Starlette และ Uvicorn | ปานกลาง (สามารถปรับปรุงได้ด้วย Gunicorn/Async) | ปานกลาง (สามารถปรับปรุงได้ด้วย Gunicorn/Async) |
| การตรวจสอบข้อมูล (Validation) | อัตโนมัติด้วย Pydantic และ Type Hints | ต้องใช้ไลบรารีภายนอก (เช่น Marshmallow, Pydantic) | ใช้ Django Forms, Django REST Framework serializers |
| เอกสาร API อัตโนมัติ | มีในตัว (Swagger UI, ReDoc) | ต้องใช้ไลบรารีภายนอก (เช่น Flask-RESTX) | ใช้ Django REST Framework, drf-spectacular |
| รองรับ Asynchronous (async/await) | รองรับเต็มรูปแบบ | รองรับบางส่วน (ตั้งแต่ Flask 2.0) | รองรับบางส่วน (ตั้งแต่ Django 3.0) |
| การจัดการ Dependency | มีระบบ Dependency Injection ในตัว | ต้องจัดการเองหรือใช้ไลบรารีภายนอก | ต้องจัดการเองหรือใช้ไลบรารีภายนอก |
| ขนาดของโค้ด | น้อย (เน้นการใช้ Type Hints) | น้อย (ตามสไตล์ Microframework) | มาก (มี Boilerplate Code และโครงสร้างที่กำหนดมาให้) |
| ความง่ายในการเรียนรู้ | ปานกลาง-ง่าย (ถ้าคุ้นเคยกับ Type Hints) | ง่าย | ปานกลาง-ยาก (มีแนวคิดเยอะที่ต้องเรียนรู้) |
| การใช้งานหลัก | Microservices, RESTful APIs, Backend สำหรับ Frontend | Microservices, เว็บไซต์ขนาดเล็ก, API | เว็บไซต์ขนาดใหญ่, CRM, E-commerce, Full-stack web apps |
จากตารางจะเห็นได้ว่า FastAPI มีข้อได้เปรียบที่ชัดเจนเมื่อพูดถึงการสร้าง REST API โดยเฉพาะในเรื่องของประสิทธิภาพ การตรวจสอบข้อมูล และเอกสาร API อัตโนมัติ ทำให้เป็นตัวเลือกที่ยอดเยี่ยมสำหรับโปรเจกต์ที่เน้น API เป็นหลักครับ
เตรียมความพร้อมก่อนเริ่มต้น
ก่อนที่เราจะเริ่มเขียนโค้ดจริง ๆ มีสิ่งเล็กน้อยที่เราต้องเตรียมความพร้อมก่อนครับ เพื่อให้การพัฒนาเป็นไปอย่างราบรื่น
Python เวอร์ชันที่แนะนำ
FastAPI ต้องการ Python เวอร์ชัน 3.7 ขึ้นไปครับ แนะนำให้ใช้เวอร์ชันล่าสุดเท่าที่จะทำได้ เช่น Python 3.9, 3.10, หรือ 3.11 เพื่อให้ได้ประสิทธิภาพและเข้าถึงคุณสมบัติใหม่ ๆ ของภาษาครับ
คุณสามารถตรวจสอบเวอร์ชัน Python ที่ติดตั้งบนเครื่องของคุณได้โดยเปิด Terminal (หรือ Command Prompt) และพิมพ์คำสั่ง:
python --version
# หรือ
python3 --version
ถ้ายังไม่มี Python ติดตั้ง หรือเวอร์ชันเก่าเกินไป แนะนำให้ดาวน์โหลดและติดตั้งจาก เว็บไซต์ทางการของ Python ครับ
Virtual Environment (สภาพแวดล้อมเสมือน)
การใช้ Virtual Environment เป็นสิ่งสำคัญและเป็นแนวปฏิบัติที่ดีในการพัฒนา Python ครับ
ทำไมต้องใช้ Virtual Environment?
- แยก Dependencies: โปรเจกต์ Python แต่ละโปรเจกต์อาจต้องการไลบรารี (packages) ที่แตกต่างกัน หรืออาจต้องการไลบรารีเวอร์ชันที่แตกต่างกัน การใช้ Virtual Environment จะช่วยให้คุณสามารถแยกชุดของไลบรารีที่จำเป็นสำหรับแต่ละโปรเจกต์ออกจากกันได้ โดยไม่ไปรบกวนไลบรารีของโปรเจกต์อื่น ๆ หรือไลบรารีของระบบครับ
- หลีกเลี่ยงความขัดแย้ง: ป้องกันปัญหา “dependency hell” ที่เกิดจากไลบรารีหลายตัวต้องการเวอร์ชันที่แตกต่างกันของไลบรารีอื่น ๆ
- ควบคุมง่าย: เมื่อคุณต้องการส่งมอบโปรเจกต์ให้กับผู้อื่น คุณก็สามารถบอกได้เลยว่าต้องติดตั้งไลบรารีอะไรบ้าง และเวอร์ชันไหน โดยไม่ต้องกังวลว่าไลบรารีเหล่านั้นจะไปกระทบกับระบบโดยรวม
Python 3 มีโมดูล venv ที่ติดตั้งมาให้พร้อมแล้ว ซึ่งเป็นวิธีที่ง่ายที่สุดในการสร้าง Virtual Environment ครับ
ติดตั้งและตั้งค่าสภาพแวดล้อม
มาเริ่มต้นสร้างโปรเจกต์ FastAPI ของเรากันเลยครับ!
สร้าง Virtual Environment
1. สร้างโฟลเดอร์สำหรับโปรเจกต์ของคุณ:
mkdir my-fastapi-app
cd my-fastapi-app
2. สร้าง Virtual Environment ภายในโฟลเดอร์โปรเจกต์ของคุณ (เราจะตั้งชื่อว่า .venv แต่คุณจะตั้งชื่ออื่นก็ได้ครับ เช่น env หรือ venv):
python -m venv .venv
3. เปิดใช้งาน Virtual Environment:
- บน macOS/Linux:
source .venv/bin/activate - บน Windows (Command Prompt):
.venv\Scripts\activate.bat - บน Windows (PowerShell):
.venv\Scripts\Activate.ps1
เมื่อเปิดใช้งานสำเร็จ คุณจะเห็นชื่อของ Virtual Environment (เช่น (.venv)) นำหน้า prompt ใน Terminal ของคุณครับ
ติดตั้ง FastAPI และ Uvicorn
เมื่อ Virtual Environment ทำงานอยู่ ให้ติดตั้ง FastAPI และ Uvicorn (ASGI server ที่ใช้รัน FastAPI) โดยใช้ pip:
pip install fastapi uvicorn[standard]
uvicorn[standard] จะติดตั้ง Uvicorn พร้อมกับ dependencies พื้นฐานที่จำเป็นครับ
FastAPI “Hello World” แรกของคุณ
มาสร้างไฟล์แรกของเรากันครับ ตั้งชื่อไฟล์ว่า main.py และใส่โค้ดต่อไปนี้:
from fastapi import FastAPI
# สร้าง instance ของ FastAPI
app = FastAPI()
# กำหนด Path Operation สำหรับ HTTP GET ที่ root path "/"
@app.get("/")
async def read_root():
return {"message": "Hello, World from FastAPI!"}
# กำหนด Path Operation สำหรับ HTTP GET ที่ path "/items/{item_id}"
# โดย {item_id} เป็น Path Parameter
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id, "message": f"You requested item {item_id}"}
คำอธิบายโค้ด:
from fastapi import FastAPI: นำเข้าคลาสFastAPIจากไลบรารี FastAPI ครับapp = FastAPI(): สร้างอินสแตนซ์ของแอปพลิเคชัน FastAPI ของเรา@app.get("/"): นี่คือ Path Operation Decorator ซึ่งบอก FastAPI ว่าฟังก์ชันread_rootด้านล่างควรถูกรันเมื่อมีการร้องขอ HTTP GET ไปยัง URL root (/)async def read_root():: ฟังก์ชันนี้จะถูกเรียกเมื่อมีคำขอ GET มายัง/คำว่าasyncระบุว่านี่คือฟังก์ชัน Asynchronous ที่สามารถทำงานแบบ Non-blocking I/O ได้return {"message": "Hello, World from FastAPI!"}: FastAPI จะแปลง Python dictionary นี้ให้เป็น JSON response โดยอัตโนมัติ@app.get("/items/{item_id}"): Path Operation นี้รับ HTTP GET ที่ path/items/ตามด้วยitem_idซึ่งเป็น Path Parameterasync def read_item(item_id: int):: FastAPI ใช้ Type Hint (item_id: int) เพื่อระบุว่าitem_idควรเป็นจำนวนเต็ม และจะทำการตรวจสอบข้อมูลให้อัตโนมัติครับ
รัน FastAPI แอปพลิเคชัน
เมื่อเรามีไฟล์ main.py แล้ว เราสามารถรันแอปพลิเคชัน FastAPI ด้วย Uvicorn ได้โดยใช้คำสั่งใน Terminal (ขณะที่ Virtual Environment ยังทำงานอยู่):
uvicorn main:app --reload
คำอธิบายคำสั่ง:
uvicorn: ชื่อของ ASGI server ที่เราติดตั้งไปmain:app: บอก Uvicorn ว่าให้หาออบเจกต์appภายในโมดูลmain.py(ไฟล์main.pyและตัวแปรapp)--reload: เป็นโหมดที่สะดวกสำหรับการพัฒนา เมื่อคุณแก้ไขโค้ดในไฟล์main.pyแล้วบันทึก Uvicorn จะรีโหลดเซิร์ฟเวอร์ให้อัตโนมัติครับ
หลังจากรันคำสั่ง คุณจะเห็นข้อความใน 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 statreload
INFO: Started server process [xxxxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
ตอนนี้ API ของคุณกำลังทำงานอยู่ที่ http://127.0.0.1:8000 ครับ!
ลองเปิดเว็บเบราว์เซอร์และเข้าไปที่:
- http://127.0.0.1:8000/ คุณจะเห็น
{"message": "Hello, World from FastAPI!"} - http://127.0.0.1:8000/items/5 คุณจะเห็น
{"item_id": 5, "message": "You requested item 5"} - ลองใส่ค่าที่ไม่ใช่ตัวเลข เช่น http://127.0.0.1:8000/items/abc คุณจะเห็นข้อผิดพลาด JSON ที่ FastAPI สร้างให้โดยอัตโนมัติ เพราะ
item_idถูกกำหนดให้เป็นintครับ
สำรวจเอกสาร API อัตโนมัติ (Swagger UI และ ReDoc)
หนึ่งในคุณสมบัติที่ยอดเยี่ยมที่สุดของ FastAPI คือการสร้างเอกสาร API แบบอินเทอร์แอคทีฟให้โดยอัตโนมัติ คุณสามารถเข้าถึงได้ที่:
- Swagger UI: http://127.0.0.1:8000/docs
- ReDoc: http://127.0.0.1:8000/redoc
ลองเข้าไปดูครับ คุณจะเห็น API ที่เราสร้างไว้ปรากฏขึ้นมาพร้อมกับรายละเอียดต่าง ๆ และสามารถทดสอบ API ได้จากหน้าเว็บนั้นได้เลย นี่ช่วยประหยัดเวลาในการทำเอกสารและทดสอบ API ได้อย่างมหาศาลครับ!
เจาะลึกแนวคิดหลักของ FastAPI
เมื่อเราเข้าใจพื้นฐานแล้ว มาสำรวจแนวคิดหลักที่ทำให้ FastAPI ทรงพลังและใช้งานง่ายกันครับ
Path Operations และ HTTP Methods
Path Operation คือฟังก์ชัน Python ที่ทำหน้าที่ประมวลผลคำขอ HTTP สำหรับ Path (URL) ที่เฉพาะเจาะจงครับ คุณกำหนด Path Operation โดยใช้ Decorator เช่น @app.get(), @app.post(), @app.put(), @app.delete(), @app.patch()
ตัวอย่าง:
from fastapi import FastAPI, status
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
# Pydantic model สำหรับ Item
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
# In-memory database (สำหรับการทดสอบเท่านั้น)
items_db = {}
item_id_counter = 0
# GET: อ่านข้อมูลทั้งหมด
@app.get("/items/", tags=["Items"])
async def read_items():
return list(items_db.values())
# GET: อ่านข้อมูลเฉพาะรายการ
@app.get("/items/{item_id}", tags=["Items"])
async def read_item(item_id: int):
if item_id not in items_db:
return {"message": "Item not found"} # ในสถานการณ์จริงควรใช้ HTTPException
return items_db[item_id]
# POST: สร้างข้อมูลใหม่
@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED, tags=["Items"])
async def create_item(item: Item):
global item_id_counter
item_id_counter += 1
items_db[item_id_counter] = item.dict() # แปลง Pydantic model เป็น dict
return {**item.dict(), "id": item_id_counter} # ส่งคืนข้อมูลที่สร้างพร้อม id
# PUT: อัปเดตข้อมูลทั้งหมด
@app.put("/items/{item_id}", response_model=Item, tags=["Items"])
async def update_item(item_id: int, item: Item):
if item_id not in items_db:
return {"message": "Item not found"} # ในสถานการณ์จริงควรใช้ HTTPException
items_db[item_id] = item.dict()
return {**item.dict(), "id": item_id}
# DELETE: ลบข้อมูล
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Items"])
async def delete_item(item_id: int):
if item_id in items_db:
del items_db[item_id]
return {} # 204 No Content มักจะส่งคืน response body ที่ว่างเปล่า
ในโค้ดข้างต้น เราได้สร้าง API สำหรับจัดการ Item โดยใช้ HTTP Methods ต่าง ๆ ครับ
tags=["Items"]: ใช้สำหรับจัดกลุ่ม Path Operation ในเอกสาร Swagger UI ครับstatus_code=status.HTTP_201_CREATED: กำหนด HTTP Status Code ที่จะส่งกลับเมื่อ Path Operation สำเร็จresponse_model=Item: กำหนดรูปแบบของข้อมูลที่จะส่งกลับเป็น Response ซึ่ง FastAPI จะตรวจสอบและแปลงข้อมูลให้อัตโนมัติ
Path Parameters
Path Parameters คือส่วนหนึ่งของ URL ที่มีค่าเปลี่ยนแปลงได้ เช่น /items/{item_id} โดย item_id คือ Path Parameter ครับ FastAPI จะใช้ Type Hint ในการตรวจสอบประเภทข้อมูลของ Path Parameter โดยอัตโนมัติ
@app.get("/users/{user_id}")
async def read_user(user_id: int): # user_id ต้องเป็น int
return {"user_id": user_id}
Query Parameters
Query Parameters คือพารามิเตอร์ที่อยู่ต่อท้าย URL หลังจากเครื่องหมาย ? เช่น /items/?skip=0&limit=10 โดย skip และ limit คือ Query Parameters ครับ
@app.get("/items/")
async def read_items_with_query(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
ในตัวอย่างนี้ skip และ limit มีค่าเริ่มต้นเป็น 0 และ 10 ตามลำดับ หากไคลเอนต์ไม่ได้ส่งค่าเหล่านี้มา FastAPI จะใช้ค่าเริ่มต้นให้ครับ
Request Body และ Pydantic Models
เมื่อต้องการส่งข้อมูลที่ซับซ้อนไปยัง API โดยเฉพาะกับ HTTP Methods อย่าง POST, PUT, PATCH เราจะใช้ Request Body ซึ่งมักจะเป็น JSON ครับ FastAPI ใช้ Pydantic Models ในการกำหนดโครงสร้างข้อมูลของ Request Body
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class Product(BaseModel):
name: str
description: Optional[str] = None
price: float
quantity: int
@app.post("/products/")
async def create_product(product: Product):
# 'product' จะเป็น instance ของ Product model ที่ถูกตรวจสอบแล้ว
return {"message": "Product created successfully", "product": product}
FastAPI จะรับ Request Body ที่เป็น JSON มาแปลงเป็นอินสแตนซ์ของคลาส Product โดยอัตโนมัติ และจะตรวจสอบข้อมูลตามที่กำหนดใน Product หากข้อมูลไม่ถูกต้องก็จะส่งข้อผิดพลาด 422 Unprocessable Entity กลับไปครับ
Response Model: กำหนดรูปแบบการตอบกลับ
คุณสามารถใช้ response_model ใน Decorator ของ Path Operation เพื่อระบุรูปแบบของ Response ที่ API จะส่งกลับไป ซึ่งจะช่วยให้ FastAPI ทำการตรวจสอบและแปลงข้อมูล Response ให้อัตโนมัติ รวมถึงสร้างเอกสาร API ที่ถูกต้องครับ
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserIn(BaseModel): # โมเดลสำหรับ Request Body (ข้อมูลที่รับเข้ามา)
username: str
password: str
email: str
class UserOut(BaseModel): # โมเดลสำหรับ Response Body (ข้อมูลที่จะส่งกลับ)
username: str
email: str
# password จะไม่ถูกรวมอยู่ใน UserOut
@app.post("/users/", response_model=UserOut)
async def create_user(user: UserIn):
# ในโลกจริง ควร hash password ก่อนบันทึกลงฐานข้อมูล
# ... บันทึกผู้ใช้ลงฐานข้อมูล ...
return user # FastAPI จะใช้ UserOut ในการกรองข้อมูลก่อนส่งกลับ
ในตัวอย่างนี้ แม้ว่าฟังก์ชัน create_user จะรับ UserIn ที่มี password แต่เนื่องจากเรากำหนด response_model=UserOut ทำให้ FastAPI จะสร้าง Response ที่มีแค่ username และ email เท่านั้น ซึ่งเป็นวิธีที่ดีในการป้องกันไม่ให้ข้อมูลที่ละเอียดอ่อนหลุดออกไปครับ
Dependency Injection: การพึ่งพิงที่ยืดหยุ่น
FastAPI มีระบบ Dependency Injection ที่ทรงพลัง ซึ่งช่วยให้คุณจัดการกับ “dependencies” (สิ่งที่เราต้องการในฟังก์ชัน Path Operation) ได้อย่างง่ายดายและมีประสิทธิภาพครับ
หลักการคือ คุณสามารถประกาศพารามิเตอร์ในฟังก์ชัน Path Operation ของคุณ และ FastAPI จะจัดการ “inject” ค่าที่ถูกต้องเข้าไปให้โดยอัตโนมัติ
ตัวอย่างการใช้งาน:
- การดึงค่า Header:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items-with-header/")
async def read_items_with_header(x_token: str = Header(...)): # ... หมายถึงพารามิเตอร์นี้จำเป็น
return {"X-Token header": x_token}
FastAPI จะดึงค่าจาก HTTP Header ชื่อ X-Token มาใส่ในตัวแปร x_token ให้อัตโนมัติ
from fastapi import FastAPI, Depends, HTTPException, status
app = FastAPI()
async def get_current_user(token: str):
# ในโลกจริง คุณจะตรวจสอบ token กับฐานข้อมูล หรือ JWT
if token != "mysecrettoken":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return {"username": "johndoe"}
@app.get("/users/me/")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
ในตัวอย่างนี้ get_current_user เป็น Dependency function ที่จะถูกเรียกก่อน read_users_me ถ้า token ไม่ถูกต้องก็จะเกิด HTTPException ถ้าถูกต้องก็จะส่งค่า {"username": "johndoe"} กลับมาให้ read_users_me ผ่านพารามิเตอร์ current_user ครับ
Dependency Injection มีประโยชน์มากในการจัดการกับการเชื่อมต่อฐานข้อมูล การตรวจสอบสิทธิ์ การดึงข้อมูลจาก Header/Cookie และอื่น ๆ ทำให้โค้ดสะอาดและนำกลับมาใช้ใหม่ได้ง่ายครับ
การจัดการข้อผิดพลาด (Error Handling)
FastAPI มีระบบการจัดการข้อผิดพลาดที่ดี คุณสามารถยก HTTPException เพื่อส่งข้อผิดพลาด HTTP มาตรฐานกลับไปยังไคลเอนต์ได้
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
headers={"X-Error": "There goes my error"}, # สามารถเพิ่ม headers ได้
)
return {"item": items[item_id]}
เมื่อไคลเอนต์ร้องขอ /items/bar ซึ่งไม่มีอยู่ใน items ก็จะได้รับ HTTP Status Code 404 Not Found พร้อมกับ JSON body ที่มี detail และ Header X-Error ครับ
การเชื่อมต่อฐานข้อมูล (SQLite & SQLAlchemy)
การสร้าง API มักจะต้องมีการจัดการข้อมูลที่จัดเก็บอยู่ในฐานข้อมูล บทความนี้เราจะใช้ SQLite เป็นฐานข้อมูลแบบง่าย ๆ และ SQLAlchemy ซึ่งเป็น ORM (Object Relational Mapper) ยอดนิยมของ Python ในการโต้ตอบกับฐานข้อมูลครับ
ตั้งค่า SQLAlchemy และ SQLite
ก่อนอื่น ติดตั้งไลบรารีที่จำเป็น:
pip install sqlalchemy python-multipart # python-multipart จำเป็นสำหรับ Forms/Files
สร้างไฟล์ database.py เพื่อจัดการการเชื่อมต่อฐานข้อมูล:
# database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# กำหนด URL ของฐานข้อมูล SQLite
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" # ไฟล์ฐานข้อมูลจะถูกสร้างในโฟลเดอร์เดียวกัน
# สร้าง SQLAlchemy Engine
# connect_args={"check_same_thread": False} จำเป็นสำหรับ SQLite ใน FastAPI
# เพราะ SQLite อนุญาตให้ thread เดียวเท่านั้นที่สามารถโต้ตอบกับฐานข้อมูลได้
# แต่ FastAPI ใช้หลาย thread
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# สร้าง SessionLocal class สำหรับการสร้าง database session
# autocommit=False หมายความว่าเราจะต้อง commit การเปลี่ยนแปลงด้วยตนเอง
# autoflush=False หมายความว่าการเปลี่ยนแปลงจะไม่ถูก flush ไปยังฐานข้อมูลจนกว่าจะ commit
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# สร้าง Base class สำหรับการสร้างโมเดลฐานข้อมูล
Base = declarative_base()
สร้างโมเดลฐานข้อมูลด้วย SQLAlchemy
สร้างไฟล์ models.py เพื่อกำหนดโครงสร้างตารางในฐานข้อมูล:
# models.py
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.orm import relationship
from .database import Base # นำเข้า Base จาก database.py
class Item(Base):
__tablename__ = "items" # ชื่อตารางในฐานข้อมูล
id = Column(Integer, primary_key=True, index=True) # คอลัมน์ id เป็น PK และมี index
title = Column(String, index=True)
description = Column(String, index=True)
การจัดการ Database Session
เราจะสร้าง Dependency สำหรับการรับ Database Session ครับ เพื่อให้แต่ละ Path Operation สามารถขอ Session ได้เมื่อต้องการ และ Session จะถูกปิดโดยอัตโนมัติเมื่อคำขอเสร็จสิ้น
เพิ่มโค้ดนี้ลงใน main.py หรือไฟล์แยกต่างหาก (เช่น dependencies.py):
# ใน main.py (หรือ dependencies.py)
from sqlalchemy.orm import Session
from .database import SessionLocal, engine, Base # นำเข้าจาก database.py และ models.py
# สร้างตารางในฐานข้อมูล (ถ้ายังไม่มี)
Base.metadata.create_all(bind=engine)
# Dependency เพื่อรับ database session
def get_db():
db = SessionLocal()
try:
yield db # ส่ง session กลับไปให้ Path Operation
finally:
db.close() # ปิด session หลังจากใช้งานเสร็จ
สร้าง CRUD Operations สำหรับ Item
ตอนนี้เราจะสร้าง Path Operations สำหรับ Create, Read, Update, Delete (CRUD) ของ Item ครับ เราจะใช้ Pydantic Models สำหรับ Request และ Response Body ด้วย
สร้างไฟล์ schemas.py สำหรับ Pydantic Models:
# schemas.py
from pydantic import BaseModel
from typing import Optional
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass # ItemCreate มีฟิลด์เหมือน ItemBase
class ItemUpdate(ItemBase):
pass # ItemUpdate ก็มีฟิลด์เหมือน ItemBase แต่ในโลกจริงอาจมีฟิลด์แตกต่างกัน
class Item(ItemBase):
id: int # เพิ่ม id เข้ามา เพราะเป็นฟิลด์ที่ได้จากฐานข้อมูล
class Config:
orm_mode = True # บอก Pydantic ให้ทำงานร่วมกับ ORM (SQLAlchemy)
ปรับปรุงไฟล์ main.py โดยรวมทุกอย่างเข้าด้วยกัน:
# main.py
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from . import models, schemas # นำเข้า models และ schemas
from .database import SessionLocal, engine # นำเข้า SessionLocal และ engine
# สร้างตารางในฐานข้อมูล (ถ้ายังไม่มี)
models.Base.metadata.create_all(bind=engine)
app = FastAPI(
title="FastAPI Item Management API",
description="A simple API for managing items with SQLAlchemy and SQLite.",
version="1.0.0",
)
# Dependency เพื่อรับ database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# --- CRUD Operations ---
Create Item (POST)
@app.post("/items/", response_model=schemas.Item, status_code=status.HTTP_201_CREATED, tags=["Items"])
async def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
db_item = models.Item(title=item.title, description=item.description)
db.add(db_item) # เพิ่ม object ลงใน session
db.commit() # บันทึกการเปลี่ยนแปลงลงฐานข้อมูล
db.refresh(db_item) # รีเฟรช object เพื่อให้ได้ id ที่ถูกสร้างโดยฐานข้อมูล
return db_item
Read Items (GET)
@app.get("/items/", response_model=List[schemas.Item], tags=["Items"])
async def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = db.query(models.Item).offset(skip).limit(limit).all()
return items
Read Single Item (GET by ID)
@app.get("/items/{item_id}", response_model=schemas.Item, tags=["Items"])
async def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(models.Item).filter(models.Item.id == item_id).first()
if item is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found")
return item
Update Item (PUT)
@app.put("/items/{item_id}", response_model=schemas.Item, tags=["Items"])
async def update_item(item_id: int, item: schemas.ItemUpdate, db: Session = Depends(get_db)):
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found")
for key, value in item.dict(exclude_unset=True).items(): # exclude_unset=True จะไม่รวมค่าที่ไม่ได้ถูกตั้งค่าในการอัปเดต
setattr(db_item, key, value) # อัปเดตฟิลด์ของ object
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
Delete Item (DELETE)
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Items"])
async def delete_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item not found")
db.delete(db_item)
db.commit()
return {} # 204 No Content
เมื่อรัน Uvicorn ด้วย uvicorn main:app --reload อีกครั้ง แล้วเปิด http://127.0.0.1:8000/docs คุณจะเห็น API สำหรับ Item ครบถ้วน พร้อมให้ทดสอบ CRUD ได้เลยครับ
นี่คือตัวอย่างการสร้าง REST API แบบครบจบด้วย FastAPI และ SQLAlchemy ซึ่งเป็นพื้นฐานสำคัญที่คุณสามารถนำไปต่อยอดสำหรับโปรเจกต์ที่ซับซ้อนยิ่งขึ้นได้ครับ
การตรวจสอบสิทธิ์และการอนุญาต (Authentication & Authorization)
การรักษาความปลอดภัยของ API เป็นสิ่งสำคัญอย่างยิ่ง FastAPI มีเครื่องมือที่ช่วยให้คุณจัดการกับการตรวจสอบสิทธิ์ (Authentication) และการอนุญาต (Authorization) ได้อย่างมีประสิทธิภาพครับ
OAuth2 และ JWT สำหรับ API Security
วิธีที่นิยมใช้สำหรับ API Security คือการใช้ OAuth2 (Open Authorization 2.0) ซึ่งเป็นเฟรมเวิร์กสำหรับการอนุญาต และมักจะใช้ร่วมกับ JWT (JSON Web Tokens)
- OAuth2: กำหนดวิธีการที่ไคลเอนต์สามารถขอและรับสิทธิ์การเข้าถึงทรัพยากรจากผู้ใช้ได้
- JWT: เป็นโทเคนเข้ารหัสที่สามารถใช้เพื่อยืนยันตัวตนของผู้ใช้และข้อมูลที่เกี่ยวข้องได้ เมื่อผู้ใช้เข้าสู่ระบบสำเร็จ เซิร์ฟเวอร์จะสร้าง JWT และส่งกลับไปให้ไคลเอนต์ จากนั้นไคลเอนต์จะส่ง JWT นี้มาพร้อมกับทุกคำขอเพื่อยืนยันตัวตน
FastAPI มีโมดูล fastapi.security ที่ช่วยให้คุณใช้งาน OAuth2 ได้ง่ายขึ้น โดยเฉพาะ OAuth2PasswordBearer ที่ใช้สำหรับ Password Flow ซึ่งเป็นวิธีที่พบบ่อยสำหรับ API ที่มีผู้ใช้ล็อกอินโดยตรงครับ
ตัวอย่างการตรวจสอบสิทธิ์ผู้ใช้เบื้องต้น
เพื่อแสดงให้เห็นถึงแนวคิดเบื้องต้น เราจะสร้างระบบตรวจสอบสิทธิ์อย่างง่ายโดยใช้ OAuth2PasswordBearer และฟังก์ชันตรวจสอบผู้ใช้ครับ
1. ติดตั้งไลบรารีสำหรับแฮชรหัสผ่าน (ถ้ายังไม่มี):
pip install passlib[bcrypt] python-jose
2. สร้างไฟล์ auth.py:
# auth.py
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
# การตั้งค่าสำหรับ JWT
SECRET_KEY = "your-secret-key" # ควรเปลี่ยนเป็นคีย์ที่ปลอดภัยและซับซ้อนใน production
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# สำหรับแฮชรหัสผ่าน
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# สำหรับ OAuth2 Password Flow
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# ฟังก์ชันสำหรับ verify password
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)
# ฟังก์ชันสำหรับสร้าง access token
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=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# ฟังก์ชันสำหรับ decode access token และดึงผู้ใช้
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
# ในโลกจริง คุณจะดึงข้อมูลผู้ใช้จากฐานข้อมูลที่นี่
# For this example, we'll just return a dummy user
user = {"username": username, "email": f"{username}@example.com"}
if user is None:
raise credentials_exception
return user
except JWTError:
raise credentials_exception
3. เพิ่ม API สำหรับ login และ Path Operation ที่ต้องมีการตรวจสอบสิทธิ์ใน main.py:
# main.py (เพิ่มโค้ดด้านล่าง)
from fastapi.security import OAuth2PasswordRequestForm
from . import auth # นำเข้า module auth
# User model (สำหรับตัวอย่างเท่านั้น, ในโลกจริงใช้ database model)
fake_users_db = {
"johndoe": {
"username": "johndoe",
"hashed_password": auth.get_password_hash("secret"),
"email": "[email protected]",
}
}
class Token(BaseModel):
access_token: str
token_type: str
@app.post("/token", response_model=Token, tags=["Authentication"])
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not auth.verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=auth.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = auth.create_access_token(
data={"sub": user["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me/", tags=["Authentication"])
async def read_users_me(current_user: dict = Depends(auth.get_current_user)):
return current_user
ตอนนี้คุณมี API สำหรับ login และ Path Operation ที่ต้องการ JWT ใน Header Authorization: Bearer <token> แล้วครับ คุณสามารถทดสอบได้จาก Swagger UI โดยการล็อกอินที่ /token เพื่อรับ Access Token แล้วนำไปใช้กับ Path Operation /users/me/ ครับ
ข้อควรพิจารณาในการ Deploy
เมื่อ API ของคุณพร้อมใช้งานแล้ว ขั้นตอนต่อไปคือการ Deploy ไปยัง Production Environment ครับ
Uvicorn และ Gunicorn
ในระหว่างการพัฒนา เราใช้ Uvicorn ในโหมด --reload ซึ่งเหมาะสำหรับการพัฒนา แต่สำหรับ Production แล้ว เราไม่ควรใช้โหมด --reload ครับ
สำหรับ Production Environment มักจะใช้ Uvicorn ร่วมกับ Gunicorn (Green Unicorn) ซึ่งเป็น WSGI HTTP Server ที่มีประสิทธิภาพ Gunicorn จะทำหน้าที่เป็น Master Process ที่จัดการ Worker Processes หลายตัว โดยแต่ละ Worker จะรัน Uvicorn เพื่อให้บริการ API ของคุณ ซึ่งจะช่วยเพิ่มประสิทธิภาพและความทนทานของแอปพลิเคชัน
ตัวอย่างการรันด้วย Gunicorn + Uvicorn:
pip install gunicorn
gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
--workers 4: กำหนดให้มี 4 Worker Processes (ปรับตามจำนวน CPU cores ที่มี)--worker-class uvicorn.workers.UvicornWorker: บอก Gunicorn ให้ใช้ Uvicorn Worker Class--bind 0.0.0.0:8000: ให้เซิร์ฟเวอร์ฟังทุก IP address ที่พอร์ต 8000
การใช้งาน Docker
Docker เป็นเครื่องมือที่ยอดเยี่ยมสำหรับการ Deploy แอปพลิเคชัน FastAPI เนื่องจากช่วยให้คุณบรรจุแอปพลิเคชันและ Dependencies ทั้งหมดลงใน Container เดียว ทำให้มั่นใจได้ว่าแอปพลิเคชันจะทำงานได้เหมือนกันในทุกสภาพแวดล้อม
ขั้นตอนเบื้องต้น:
- สร้างไฟล์
Dockerfile - สร้างไฟล์
requirements.txt(pip freeze > requirements.txt) - Build Docker Image
- Run Docker Container
การใช้ Docker ช่วยให้การ Deploy ง่ายขึ้นและลดปัญหา “works on my machine” ได้อย่างมากครับ
แพลตฟอร์มคลาวด์
คุณสามารถ Deploy FastAPI API ของคุณไปยังแพลตฟอร์มคลาวด์ยอดนิยมได้หลากหลาย เช่น:
- Heroku: ง่ายต่อการเริ่มต้น เหมาะสำหรับโปรเจกต์ขนาดเล็กถึงกลาง
- AWS (Amazon Web Services): EC2, ECS, EKS, Lambda (สำหรับ Serverless API)
- Google Cloud Platform (GCP): Compute Engine, Cloud Run, App Engine
- Microsoft Azure: Azure App Service, Azure Container Instances
แต่ละแพลตฟอร์มมีข้อดีข้อเสียและวิธีการ Deploy ที่แตกต่างกันไป ขึ้นอยู่กับความต้องการและขนาดของโปรเจกต์ของคุณครับ
หัวข้อขั้นสูงเพิ่มเติม
FastAPI มีคุณสมบัติขั้นสูงอีกมากมายที่สามารถช่วยให้คุณสร้าง API ที่ซับซ้อนและมีประสิทธิภาพได้:
Background Tasks
สำหรับงานที่ใช้เวลานานและไม่ต้องการให้ไคลเอนต์รอการตอบกลับ เช่น การส่งอีเมล การประมวลผลข้อมูลขนาดใหญ่ คุณสามารถใช้ Background Tasks ของ FastAPI ได้ครับ
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def write_log(message: str):
with open("log.txt", mode="a") as log:
log.write(message + "\n")
@app.post("/send-notification/")
async def send_notification(email: str, background_tasks: BackgroundTasks):
message = f"Notification sent to {email}"
background_tasks.add_task(write_log, message) # เพิ่ม task ลงใน background
return {"message": "Notification will be sent in the background"}
Middleware
Middleware คือฟังก์ชันที่สามารถทำงานก่อนและหลัง Path Operation ของคุณได้ มีประโยชน์สำหรับการประมวลผลคำขอหรือการตอบกลับทั่วไป เช่น การล็อกคำขอ, การบีบอัด Response, การจัดการ CORS
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
app = FastAPI()
# Custom Middleware (ตัวอย่างการเพิ่ม Header)
class CustomHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers["X-Custom-Header"] = "Hello from FastAPI"
return response
app.add_middleware(CustomHeaderMiddleware)
# CORS Middleware (สำคัญสำหรับการพัฒนา Frontend)
origins = [
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(