วิธีสร้าง REST API ด้วย FastAPI Python แบบครบจบ

ในโลกของการพัฒนาซอฟต์แวร์ที่ขับเคลื่อนด้วยข้อมูลและบริการที่เชื่อมโยงกัน การสร้าง Application Programming Interface (API) กลายเป็นหัวใจสำคัญที่ช่วยให้ระบบต่างๆ สามารถสื่อสารและแลกเปลี่ยนข้อมูลกันได้อย่างมีประสิทธิภาพครับ และเมื่อพูดถึงการสร้าง RESTful API ด้วย Python ในยุคปัจจุบัน หลายคนคงได้ยินชื่อของ FastAPI ซึ่งเป็นเฟรมเวิร์กที่มาแรง ด้วยประสิทธิภาพที่ยอดเยี่ยม ความเร็วในการพัฒนาที่น่าทึ่ง และระบบเอกสารอัตโนมัติที่ครบครัน บทความนี้จะพาทุกท่านไปเจาะลึกถึงวิธีการสร้าง REST API ด้วย FastAPI ตั้งแต่พื้นฐานไปจนถึงการเชื่อมต่อฐานข้อมูล การจัดการสิทธิ์ และการนำไปใช้งานจริงแบบครบจบทุกขั้นตอน เพื่อให้คุณสามารถนำความรู้ไปประยุกต์ใช้สร้าง API ที่แข็งแกร่งและมีประสิทธิภาพสำหรับโปรเจกต์ของคุณได้อย่างมั่นใจครับ

สารบัญ

ทำไมต้องสร้าง REST API?

ก่อนที่เราจะลงลึกถึงการสร้าง API ด้วย FastAPI เรามาทำความเข้าใจพื้นฐานกันก่อนว่า REST API คืออะไร และทำไมการสร้าง API จึงมีความสำคัญในการพัฒนาแอปพลิเคชันยุคใหม่ครับ

REST API คืออะไร?

REST (Representational State Transfer) ไม่ใช่ภาษาโปรแกรมหรือเฟรมเวิร์ก แต่เป็น สถาปัตยกรรม (Architectural Style) สำหรับการออกแบบระบบเครือข่าย โดยเฉพาะอย่างยิ่ง World Wide Web ครับ ซึ่งถูกคิดค้นโดย Roy Fielding ในปี 2000 เพื่อให้ระบบต่างๆ สามารถสื่อสารกันได้ง่ายและมีมาตรฐาน

หลักการสำคัญของ REST คือการมองทุกสิ่งเป็น Resource (ทรัพยากร) เช่น ผู้ใช้งาน (User), สินค้า (Product), หรือโพสต์ (Post) และแต่ละ Resource จะมี URI (Uniform Resource Identifier) เป็นของตัวเองที่สามารถระบุตัวตนได้ครับ การดำเนินการกับ Resource เหล่านี้จะใช้มาตรฐานของ HTTP Methods ได้แก่:

  • GET: สำหรับดึงข้อมูลจาก Resource
  • POST: สำหรับสร้าง Resource ใหม่
  • PUT: สำหรับอัปเดต Resource ทั้งหมด
  • PATCH: สำหรับอัปเดต Resource บางส่วน
  • DELETE: สำหรับลบ Resource

ข้อมูลที่ถูกส่งไปมาระหว่าง Client (เช่น เว็บเบราว์เซอร์, โมบายล์แอป) และ Server มักจะอยู่ในรูปแบบ JSON (JavaScript Object Notation) ซึ่งเป็นรูปแบบที่อ่านง่ายและเป็นที่นิยมอย่างแพร่หลายครับ

ประโยชน์ของ REST API

การสร้าง REST API มีประโยชน์มากมายที่ทำให้เป็นที่นิยมในการพัฒนาซอฟต์แวร์ครับ

  • มาตรฐานและเข้าใจง่าย: การใช้ HTTP Methods และสถานะ (Status Codes) แบบมาตรฐาน ทำให้ Developer จากทีมต่างๆ สามารถเข้าใจและใช้งาน API ได้ง่าย
  • แยกส่วนการทำงาน (Separation of Concerns): REST API ช่วยให้ Frontend (ส่วนแสดงผล) และ Backend (ส่วนประมวลผลข้อมูล) ทำงานแยกจากกันได้อย่างอิสระ ทำให้การพัฒนาและการดูแลรักษาง่ายขึ้น ทีมสามารถทำงานพร้อมกันได้โดยไม่รบกวนกันครับ
  • ความยืดหยุ่น (Flexibility): API เดียวสามารถรองรับ Client ได้หลายประเภท ไม่ว่าจะเป็น Web Application, Mobile Application, หรือแม้แต่ IoT Devices
  • ความสามารถในการขยายขนาด (Scalability): เนื่องจากเป็น Stateless (เซิร์ฟเวอร์ไม่เก็บข้อมูลสถานะของ Client ระหว่าง Request) ทำให้สามารถเพิ่ม Server เพื่อรองรับ Load ที่เพิ่มขึ้นได้ง่าย
  • ประสิทธิภาพ: การใช้ JSON เป็นรูปแบบข้อมูลทำให้มีขนาดเล็กและส่งผ่านเครือข่ายได้อย่างรวดเร็ว

ทำไมต้องเลือก FastAPI สำหรับการสร้าง API?

เมื่อเข้าใจถึงความสำคัญของ REST API แล้ว คำถามต่อไปคือ “ทำไมต้องเป็น FastAPI?” ในขณะที่มีเฟรมเวิร์ก Python อื่นๆ อีกมากมายที่สามารถสร้าง API ได้เช่นกันครับ

FastAPI คืออะไร?

FastAPI เป็นเฟรมเวิร์กเว็บประสิทธิภาพสูงสำหรับสร้าง API ด้วย Python 3.7+ โดยเฉพาะ ถูกออกแบบมาให้ง่ายต่อการใช้งานและรวดเร็วในการพัฒนาครับ FastAPI สร้างขึ้นบนรากฐานของสองไลบรารีที่ทรงพลังคือ:

  • Starlette: เฟรมเวิร์ก ASGI (Asynchronous Server Gateway Interface) ขนาดเล็กและเร็วมาก สำหรับการสร้างเว็บเซิร์ฟเวอร์ที่รองรับ Asynchronous I/O (async/await)
  • Pydantic: ไลบรารีสำหรับการจัดการข้อมูลด้วย Python type hints ซึ่งช่วยในการตรวจสอบความถูกต้องของข้อมูล (Data Validation) และการจัดรูปแบบข้อมูล (Serialization)

การผสมผสานสองสิ่งนี้เข้าด้วยกัน ทำให้ FastAPI มีความเร็วเทียบเท่ากับ Node.js และ Go ในบางกรณี และมีฟีเจอร์ครบครันที่ช่วยให้การพัฒนา API เป็นเรื่องง่ายและมีประสิทธิภาพครับ

จุดเด่นของ FastAPI

FastAPI มีจุดเด่นหลายประการที่ทำให้เป็นตัวเลือกที่น่าสนใจสำหรับนักพัฒนาครับ

  • ประสิทธิภาพสูง: เนื่องจากสร้างบน Starlette และรองรับ Asynchronous I/O ทำให้ FastAPI สามารถจัดการ Request จำนวนมากได้อย่างรวดเร็วครับ
  • ง่ายต่อการเรียนรู้และใช้งาน: การใช้ Python type hints ช่วยให้โค้ดสะอาด อ่านง่าย และลดโอกาสเกิดข้อผิดพลาด
  • เอกสาร API อัตโนมัติ (Automatic Interactive API Docs): นี่คือหนึ่งในฟีเจอร์ที่โดดเด่นที่สุด FastAPI สร้างเอกสาร API แบบอินเทอร์แอคทีฟให้โดยอัตโนมัติ (Swagger UI และ ReDoc) จากโค้ดของเรา ทำให้ทีม Frontend และผู้ใช้งาน API สามารถเข้าใจและทดสอบ API ได้ทันทีโดยไม่ต้องเขียนเอกสารเพิ่ม
  • การตรวจสอบข้อมูล (Data Validation): ด้วย Pydantic, FastAPI สามารถตรวจสอบความถูกต้องของข้อมูลที่เข้ามาใน Request และออก Error ที่ชัดเจนหากข้อมูลไม่ถูกต้อง
  • การฉีดการพึ่งพิง (Dependency Injection): ระบบ DI ที่ทรงพลังช่วยให้จัดการโค้ดที่ต้องใช้ร่วมกัน เช่น การเชื่อมต่อฐานข้อมูล หรือการตรวจสอบสิทธิ์ ได้อย่างง่ายดายและเป็นระเบียบ
  • รองรับ Asynchronous (Async/Await): สามารถเขียนโค้ดที่ทำงานแบบ Non-blocking I/O ได้ง่ายๆ ช่วยเพิ่มประสิทธิภาพในการจัดการ Request ที่ต้องรอการตอบกลับจากภายนอก
  • ความปลอดภัย: มีเครื่องมือสำหรับจัดการความปลอดภัย เช่น การตรวจสอบสิทธิ์ (Authentication) และการอนุญาต (Authorization) ด้วย OAuth2, JWT และอื่นๆ

เปรียบเทียบ FastAPI กับเฟรมเวิร์ก Python อื่นๆ

เพื่อให้เห็นภาพชัดเจนขึ้น เราลองมาดูการเปรียบเทียบ FastAPI กับเฟรมเวิร์ก Python ยอดนิยมอื่นๆ อย่าง Flask และ Django REST Framework ครับ

คุณสมบัติ FastAPI Flask Django REST Framework (DRF)
แนวคิดหลัก API-first, Asynchronous, Type-hint driven Microframework, เน้นความยืดหยุ่น, Minimalist Full-stack web framework (Django) + API features
ประสิทธิภาพ สูงมาก (สร้างบน Starlette, Pydantic) ปานกลาง (WSGI), สามารถใช้ Async กับบาง Extension ได้ ปานกลาง (WSGI), เน้นความครบครัน
Data Validation Built-in (Pydantic), ใช้ Type Hints ต้องใช้ไลบรารีภายนอก (เช่น Marshmallow) Built-in (Serializers)
เอกสาร API อัตโนมัติ (Swagger UI, ReDoc) ต้องใช้ไลบรารีภายนอก หรือเขียนเอง อัตโนมัติบางส่วน (Browsing API), ต้องใช้ไลบรารีภายนอกสำหรับ Swagger
การเรียนรู้ ปานกลาง-ง่าย (ถ้าคุ้นเคยกับ Type Hints) ง่าย (Microframework) ปานกลาง-ยาก (ต้องเรียนรู้ Django ก่อน)
Asynchronous Built-in (Async/Await) รองรับแบบจำกัด (ต้องใช้ ASGI server และ Extensions) รองรับแบบจำกัด (เริ่มเพิ่มใน Django 3.x)
Dependency Injection Built-in, ทรงพลัง ต้องใช้ไลบรารีภายนอก หรือเขียนเอง มีรูปแบบของตัวเอง (เช่น Middleware)
เหมาะสำหรับ สร้าง RESTful API, Microservices, Real-time services โปรเจกต์ขนาดเล็ก, REST API ง่ายๆ, Prototype Web Application ขนาดใหญ่, REST API ที่ซับซ้อน (เมื่อใช้ร่วมกับ Django)

จากตารางจะเห็นได้ว่า FastAPI มีจุดเด่นในเรื่องประสิทธิภาพ, Data Validation, และการสร้างเอกสารอัตโนมัติ ซึ่งเป็นสิ่งสำคัญสำหรับการสร้าง API ที่มีคุณภาพและรวดเร็วครับ

เตรียมความพร้อมก่อนเริ่ม

ก่อนที่เราจะเริ่มเขียนโค้ดจริง เรามาเตรียมสภาพแวดล้อมการทำงานให้พร้อมกันก่อนนะครับ การเตรียมการที่ดีจะช่วยให้การพัฒนาเป็นไปอย่างราบรื่นครับ

ติดตั้ง Python

FastAPI ต้องการ Python เวอร์ชัน 3.7 ขึ้นไปครับ ถ้าคุณยังไม่มี Python ติดตั้งอยู่ในเครื่อง สามารถดาวน์โหลดได้จากเว็บไซต์ทางการของ Python (python.org) และทำตามขั้นตอนการติดตั้งให้เรียบร้อยครับ

หลังจากติดตั้งเสร็จแล้ว ลองตรวจสอบเวอร์ชันของ Python ใน Command Line หรือ Terminal ของคุณ:

python --version
# หรือบางระบบอาจใช้
python3 --version

คุณควรเห็นผลลัพธ์ประมาณ Python 3.x.x ครับ

สร้าง Virtual Environment

การสร้าง Virtual Environment เป็นแนวทางปฏิบัติที่ดีที่สุดในการพัฒนาโปรเจกต์ Python ครับ เพราะจะช่วยแยก Dependencies ของแต่ละโปรเจกต์ออกจากกัน ไม่ให้รบกวนกันเองครับ

ในโฟลเดอร์โปรเจกต์ของคุณ (เช่น my-fastapi-app) ให้รันคำสั่งต่อไปนี้เพื่อสร้าง Virtual Environment:

mkdir my-fastapi-app
cd my-fastapi-app
python -m venv venv

จากนั้น Activate Virtual Environment:

  • บน Windows:
    .\venv\Scripts\activate
  • บน macOS/Linux:
    source venv/bin/activate

เมื่อ Virtual Environment ถูก Activate แล้ว คุณจะเห็น (venv) นำหน้า Command Line ของคุณครับ

ติดตั้ง FastAPI และ Uvicorn

ตอนนี้เราพร้อมที่จะติดตั้ง FastAPI และ Uvicorn แล้วครับ

  • FastAPI: ตัวเฟรมเวิร์กหลัก
  • Uvicorn: ASGI server ที่ใช้รันแอปพลิเคชัน FastAPI (FastAPI ไม่ได้มาพร้อมกับ Server ในตัวเหมือน Django)

รันคำสั่งนี้ในขณะที่ Virtual Environment ของคุณยัง Active อยู่ครับ:

pip install fastapi "uvicorn[standard]"

"uvicorn[standard]" จะติดตั้ง Uvicorn พร้อมกับ Dependencies ที่จำเป็น เช่น httptools และ watchfiles เพื่อประสิทธิภาพที่ดีขึ้นและฟังก์ชันการ Reload อัตโนมัติครับ

คุณสามารถบันทึก Dependencies ของโปรเจกต์ลงในไฟล์ requirements.txt เพื่อให้โปรเจกต์สามารถติดตั้งได้ง่ายในอนาคตครับ:

pip freeze > requirements.txt

สร้าง REST API แรกของคุณด้วย FastAPI

เมื่อเตรียมความพร้อมเรียบร้อยแล้ว ได้เวลาลงมือสร้าง API แรกกันแล้วครับ เราจะเริ่มต้นด้วย API “Hello World” ที่ง่ายที่สุด

โครงสร้างไฟล์เบื้องต้น

ภายในโฟลเดอร์ my-fastapi-app ที่เราสร้างไว้ ให้สร้างไฟล์ชื่อ main.py ขึ้นมาครับ

my-fastapi-app/
├── venv/
└── main.py

Hello World API

เปิดไฟล์ main.py แล้วใส่โค้ดต่อไปนี้ครับ

# main.py
from fastapi import FastAPI

# สร้าง Instance ของ FastAPI
app = FastAPI()

# กำหนด Path Operation Decorator สำหรับ HTTP GET Request ที่ Root URL ("/")
@app.get("/")
async def read_root():
    """
    Endpoint สำหรับทดสอบว่า API ทำงานได้หรือไม่
    คืนค่าเป็นข้อความ "Hello, World!"
    """
    return {"message": "Hello, World!"}

# กำหนด Path Operation Decorator สำหรับ HTTP GET Request ที่ URL "/items/{item_id}"
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
    """
    Endpoint สำหรับดึงข้อมูล Item ด้วย item_id
    และสามารถใช้ Query Parameter 'q' เพื่อกรองข้อมูลได้
    """
    return {"item_id": item_id, "q": q}

คำอธิบายโค้ด:

  • from fastapi import FastAPI: นำเข้าคลาส FastAPI
  • app = FastAPI(): สร้าง Instance ของแอปพลิเคชัน FastAPI
  • @app.get("/"): นี่คือ Path Operation Decorator ซึ่งบอก FastAPI ว่าฟังก์ชัน read_root ด้านล่างนี้ควรถูกเรียกเมื่อมี HTTP GET Request มาที่ Root URL (/)
  • async def read_root():: การใช้ async def แสดงว่าฟังก์ชันนี้เป็นฟังก์ชัน Asynchronous ซึ่งเป็นคุณสมบัติหลักของ FastAPI ที่ช่วยให้ประสิทธิภาพสูงขึ้นครับ
  • return {"message": "Hello, World!"}: FastAPI จะแปลง Python dictionary นี้ให้เป็น JSON Response โดยอัตโนมัติ
  • สำหรับ @app.get("/items/{item_id}"):
    • {item_id} ใน Path เป็น Path Parameter ซึ่งหมายความว่าค่าที่อยู่ในตำแหน่งนี้ของ URL จะถูกส่งเป็น Argument ไปยังฟังก์ชัน read_item
    • item_id: int: เราใช้ Python type hint (: int) เพื่อบอก FastAPI ว่า item_id ควรจะเป็น Integer FastAPI จะทำการตรวจสอบและแปลงค่าให้โดยอัตโนมัติครับ
    • q: str = None: นี่คือ Query Parameter ที่เป็น Optional (ไม่จำเป็นต้องมี) และมีค่า Default เป็น None

การรัน API ของคุณ

เปิด Command Line/Terminal ในโฟลเดอร์ my-fastapi-app (ตรวจสอบให้แน่ใจว่า Virtual Environment ยัง Active อยู่) แล้วรันคำสั่งนี้ครับ:

uvicorn main:app --reload

คำอธิบายคำสั่ง:

  • uvicorn: คือ ASGI server ที่เราติดตั้งไป
  • main:app: บอก Uvicorn ว่าให้โหลดแอปพลิเคชัน app จากไฟล์ main.py (ชื่อไฟล์คือ main, ชื่อ Instance คือ app)
  • --reload: โหมดนี้จะทำให้ Server รีโหลดโดยอัตโนมัติเมื่อมีการเปลี่ยนแปลงโค้ดในไฟล์โปรเจกต์ เหมาะสำหรับการพัฒนาครับ

คุณจะเห็นข้อความคล้ายๆ แบบนี้ใน Terminal:

INFO:     Will watch for changes in these directories: ['/path/to/my-fastapi-app']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [xxxxx] using WatchFiles
INFO:     Started server process [xxxxx]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

ตอนนี้ API ของคุณกำลังทำงานอยู่ที่ http://127.0.0.1:8000 ครับ

ทดสอบ API และเอกสารอัตโนมัติ

เปิดเว็บเบราว์เซอร์และลองเข้าถึง URL เหล่านี้:

  • http://127.0.0.1:8000: คุณควรเห็น {"message": "Hello, World!"}
  • http://127.0.0.1:8000/items/5: คุณควรเห็น {"item_id": 5, "q": null}
  • http://127.0.0.1:8000/items/10?q=somequery: คุณควรเห็น {"item_id": 10, "q": "somequery"}

และที่น่าทึ่งที่สุดคือเอกสาร API อัตโนมัติครับ:

  • Swagger UI: เข้าไปที่ http://127.0.0.1:8000/docs คุณจะเห็นหน้าเอกสาร API แบบอินเทอร์แอคทีฟที่สามารถทดสอบ API ได้ทันที
  • ReDoc: เข้าไปที่ http://127.0.0.1:8000/redoc คุณจะเห็นเอกสาร API ที่อ่านง่ายและสวยงามอีกรูปแบบหนึ่ง

นี่คือจุดแข็งสำคัญของ FastAPI ที่ช่วยประหยัดเวลาในการเขียนเอกสารและทำให้การทำงานร่วมกันง่ายขึ้นมากครับ

การจัดการ HTTP Methods

เราได้ลองใช้ HTTP GET ไปแล้ว มาดูกันว่า FastAPI จัดการกับ HTTP Methods อื่นๆ ที่เหลืออย่างไรบ้างครับ

GET: การดึงข้อมูล

สำหรับการดึงข้อมูล เราใช้ @app.get() ครับ เราได้เห็นตัวอย่างไปแล้ว แต่มาดูตัวอย่างเพิ่มเติมเกี่ยวกับการใช้ Path Parameters และ Query Parameters อย่างละเอียดครับ

# main.py (ต่อจากโค้ดเดิม)

@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: str, short: bool = False):
    """
    ดึงข้อมูล Item ของ User คนใดคนหนึ่ง
    - user_id: Path Parameter (int)
    - item_id: Path Parameter (str)
    - short: Query Parameter (bool) - ถ้าเป็น True จะคืนข้อมูลบางส่วน
    """
    item = {"item_id": item_id, "owner_id": user_id}
    if not short:
        item.update(
            {"description": "This is an amazing item that belongs to user "
                            f"{user_id} and has ID {item_id}"}
        )
    return item

ในตัวอย่างนี้ เราใช้ Path Parameters สองตัว (user_id และ item_id) และ Query Parameter หนึ่งตัว (short) ที่เป็น Boolean ครับ FastAPI จะทำการแปลงค่าจาก URL ให้เป็น Type ที่ถูกต้องโดยอัตโนมัติ

POST: การสร้างข้อมูล

สำหรับการสร้างข้อมูลใหม่ เราใช้ @app.post() และมักจะส่งข้อมูลในรูปแบบ Request Body ครับ FastAPI ใช้ Pydantic ในการจัดการ Request Body ได้อย่างยอดเยี่ยมครับ

# main.py (ต่อจากโค้ดเดิม)
from typing import Optional
from pydantic import BaseModel

# กำหนด Pydantic Model สำหรับข้อมูล Item
class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.post("/items/")
async def create_item(item: Item):
    """
    สร้าง Item ใหม่ โดยรับข้อมูล Item จาก Request Body
    """
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict

คำอธิบาย:

  • เราสร้างคลาส Item ที่สืบทอดมาจาก BaseModel ของ Pydantic เพื่อกำหนดโครงสร้างข้อมูลที่เราคาดหวังจาก Request Body
  • name: str: field name เป็น String และต้องมี
  • description: Optional[str] = None: field description เป็น String แต่เป็น Optional และมีค่า Default เป็น None
  • price: float: field price เป็น Float และต้องมี
  • tax: Optional[float] = None: field tax เป็น Float แต่เป็น Optional
  • ใน create_item ฟังก์ชัน เราประกาศ Argument item: Item ซึ่ง FastAPI จะรู้ทันทีว่านี่คือ Request Body และจะใช้ Pydantic ในการตรวจสอบความถูกต้องของข้อมูลที่ส่งมาครับ
  • ถ้าข้อมูลที่ส่งมาไม่ตรงกับ Model (เช่น ไม่มี name หรือ price) FastAPI จะคืนค่า Error Response ที่มีรายละเอียดให้โดยอัตโนมัติ

PUT: การอัปเดตข้อมูลทั้งหมด

สำหรับการอัปเดตข้อมูลทั้งหมดของ Resource ที่มีอยู่ เราใช้ @app.put() ครับ

# main.py (ต่อจากโค้ดเดิม)

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    """
    อัปเดตข้อมูล Item ทั้งหมดสำหรับ item_id ที่ระบุ
    """
    results = {"item_id": item_id, **item.dict()}
    return results

ในตัวอย่างนี้ เราต้องการอัปเดต Item ด้วย item_id ที่ระบุ โดยรับข้อมูลใหม่ทั้งหมดของ Item จาก Request Body ครับ

PATCH: การอัปเดตข้อมูลบางส่วน

สำหรับการอัปเดตข้อมูลบางส่วน (Partial Update) เราใช้ @app.patch() ครับ ซึ่งมักจะซับซ้อนกว่า PUT เล็กน้อย เพราะต้องจัดการกับ Field ที่ไม่ได้ส่งมา

# main.py (ต่อจากโค้ดเดิม)

# สร้าง Pydantic Model สำหรับการอัปเดตบางส่วน
# กำหนด Field ทั้งหมดเป็น Optional
class ItemUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: Optional[float] = None

@app.patch("/items/{item_id}")
async def patch_item(item_id: int, item_update: ItemUpdate):
    """
    อัปเดตข้อมูล Item บางส่วนสำหรับ item_id ที่ระบุ
    """
    # ในโลกจริง เราจะต้องดึง item ปัจจุบันจากฐานข้อมูลก่อน
    # แล้วค่อยอัปเดตเฉพาะ field ที่มีค่าอยู่ใน item_update
    
    # ตัวอย่างจำลอง: ดึง item เดิม (สมมติว่ามีอยู่แล้ว)
    existing_item = {"name": "Old Name", "description": "Old description", "price": 100.0, "tax": 10.0}

    # อัปเดตเฉพาะ field ที่มีค่าส่งมาใน item_update
    updated_data = item_update.dict(exclude_unset=True) # exclude_unset=True จะไม่รวม field ที่ไม่ได้ถูกตั้งค่า
    
    for key, value in updated_data.items():
        existing_item[key] = value

    return {"item_id": item_id, **existing_item}

เราสร้าง ItemUpdate Model ที่ทุก Field เป็น Optional เพื่อให้ FastAPI ทราบว่าข้อมูลที่ส่งมาอาจมีแค่บางส่วนครับ และใช้ item_update.dict(exclude_unset=True) เพื่อกรองเฉพาะ Field ที่ถูกส่งมาจริงๆ ครับ

DELETE: การลบข้อมูล

สำหรับการลบ Resource เราใช้ @app.delete() ครับ

# main.py (ต่อจากโค้ดเดิม)
from fastapi import HTTPException, status

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    """
    ลบ Item ด้วย item_id ที่ระบุ
    """
    # ในโลกจริง เราจะทำการลบ item จากฐานข้อมูล
    
    # ตัวอย่างจำลอง: ตรวจสอบว่า item_id มีอยู่จริงหรือไม่
    if item_id not in [1, 2, 3]: # สมมติว่ามีแค่ item_id 1, 2, 3
        raise HTTPException(status_code=404, detail="Item not found")

    # ถ้าลบสำเร็จ ก็ไม่ต้องคืนค่าอะไร แต่กำหนด Status Code เป็น 204 No Content
    return

เรากำหนด status_code=status.HTTP_204_NO_CONTENT ซึ่งเป็น Status Code มาตรฐานสำหรับ HTTP DELETE ที่สำเร็จโดยไม่มีการคืน Content ใดๆ ครับ และมีการใช้ HTTPException เพื่อจัดการกรณีที่ Item ไม่พบด้วย

การใช้ Pydantic สำหรับ Data Validation และ Serialization

Pydantic เป็นหัวใจสำคัญที่ทำให้ FastAPI ทรงพลังในเรื่องการจัดการข้อมูลครับ เราได้เห็นการใช้งาน Pydantic BaseModel ไปบ้างแล้วในส่วนของ HTTP POST แต่มาทำความเข้าใจให้ลึกซึ้งยิ่งขึ้นครับ

การสร้าง Models

Pydantic Models ช่วยให้เรากำหนดโครงสร้างข้อมูลที่คาดหวังได้อย่างชัดเจน โดยใช้ Python type hints ครับ

# models.py (สร้างไฟล์ใหม่เพื่อจัดระเบียบโค้ด)
from typing import Optional, List
from pydantic import BaseModel, Field, HttpUrl

class UserBase(BaseModel):
    username: str
    email: str
    full_name: Optional[str] = None

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int
    is_active: bool = True # สามารถกำหนดค่า default ได้
    items: List['Item'] = [] # สามารถอ้างอิง Model อื่นๆ ได้ (ต้องเป็น string ถ้ายังไม่ได้ประกาศ)

    class Config:
        orm_mode = True # สำคัญมากสำหรับการเชื่อมต่อกับ ORM (เช่น SQLAlchemy)

class ItemBase(BaseModel):
    title: str = Field(..., example="The amazing item") # ... หมายถึง Required, example สำหรับ Docs
    description: Optional[str] = Field(None, example="A very useful item")
    price: float = Field(..., gt=0, description="Price must be greater than zero") # gt = greater than
    tags: List[str] = []

class ItemCreate(ItemBase):
    pass

class Item(ItemBase):
    id: int
    owner_id: int

    class Config:
        orm_mode = True

# ต้องประกาศหลังจาก Item เพื่อให้ User สามารถอ้างอิงได้
User.update_forward_refs()

คำอธิบาย:

  • BaseModel: คลาสหลักที่ใช้สร้าง Pydantic Model
  • Field(...): ใช้สำหรับกำหนดคุณสมบัติเพิ่มเติมของ Field เช่น example สำหรับเอกสาร, gt (greater than) สำหรับการตรวจสอบค่า
  • Optional[str] = None: ระบุว่า Field นั้นเป็น Optional และมีค่า Default เป็น None
  • List['Item'] = []: สามารถกำหนด Field ที่เป็น List ของ Model อื่นๆ ได้ การใช้ 'Item' เป็น String แทนที่จะเป็น Item โดยตรง เป็นวิธีที่เรียกว่า “Forward Reference” เพื่อหลีกเลี่ยง Circular Dependency ในกรณีที่ Models อ้างอิงถึงกันครับ
  • class Config: orm_mode = True: Property นี้สำคัญมากเมื่อเราจะเชื่อมต่อกับฐานข้อมูล โดยเฉพาะกับ ORM อย่าง SQLAlchemy ครับ มันจะบอก Pydantic ให้พยายามอ่านข้อมูลจาก Attributes ของ Object แทนที่จะเป็น Dictionary Keys ทำให้เราสามารถส่ง SQLAlchemy Model Object เข้าไปใน Pydantic Model ได้โดยตรง
  • User.update_forward_refs(): จำเป็นต้องเรียกเพื่อแก้ไข Forward References หลังจากที่ Model ทั้งหมดถูกประกาศแล้วครับ

การใช้งานใน Request Body

เราได้เห็นไปแล้วว่าสามารถประกาศ Pydantic Model เป็น Type ของ Argument ใน Path Operation Function ได้ครับ

# main.py (ส่วนหนึ่ง)
from .models import ItemCreate # สมมติว่า models.py อยู่ในโฟลเดอร์เดียวกัน

@app.post("/items/", response_model=Item) # กำหนด response_model ด้วย
async def create_item(item: ItemCreate):
    # ตรรกะการสร้าง item ในฐานข้อมูล
    # ...
    fake_item_id = len(fake_db["items"]) + 1 # สร้าง ID จำลอง
    db_item = {"id": fake_item_id, "owner_id": 1, **item.dict()}
    fake_db["items"].append(db_item)
    return db_item # คืนค่าเป็น dictionary ที่จะถูกแปลงเป็น Item Model โดยอัตโนมัติ

การใช้งานใน Response Model

FastAPI ยังสามารถใช้ Pydantic Model เพื่อกำหนดโครงสร้างของ Response ที่จะส่งกลับไปยัง Client ได้ด้วยการใช้ Argument response_model ใน Path Operation Decorator ครับ

# main.py (ส่วนหนึ่ง)
from .models import Item # นำเข้า Item Model

# ...
@app.get("/items/{item_id}", response_model=Item)
async def read_item_by_id(item_id: int):
    """
    ดึงข้อมูล Item ด้วย item_id และคืนค่าเป็น Item Model
    """
    # สมมติว่าดึงมาจากฐานข้อมูล
    fake_db = {"items": [{"id": 1, "title": "Foo", "description": "Desc1", "price": 10.0, "tags": ["tag1"], "owner_id": 1}]}
    for item in fake_db["items"]:
        if item["id"] == item_id:
            return item
    raise HTTPException(status_code=404, detail="Item not found")

ประโยชน์ของ response_model:

  • Data Filtering: ถ้าคุณคืนค่า Object ที่มี Field มากกว่าที่ response_model กำหนด FastAPI จะส่งคืนเฉพาะ Field ที่อยู่ใน response_model เท่านั้น (ช่วยในการซ่อนข้อมูลที่ไม่จำเป็น)
  • Data Type Conversion: FastAPI จะตรวจสอบและแปลงข้อมูลให้ตรงกับ Type ที่กำหนดใน response_model
  • Automatic Documentation: เอกสาร API จะแสดงโครงสร้างของ Response ที่ถูกต้องตาม Pydantic Model

การใช้ Pydantic ช่วยให้โค้ดของคุณมีความปลอดภัย มีประสิทธิภาพ และมีเอกสารประกอบที่ชัดเจนโดยไม่ต้องเสียเวลาเขียนเองครับ

Path Parameters และ Query Parameters

เราได้แตะเรื่องนี้ไปบ้างแล้ว แต่มาเจาะลึกเพิ่มเติมเกี่ยวกับการใช้งาน Path Parameters และ Query Parameters ใน FastAPI ครับ

Path Parameters

Path Parameters ใช้สำหรับระบุ Resource ที่เฉพาะเจาะจง มักเป็นส่วนหนึ่งของ URL Path ครับ

# main.py (ตัวอย่าง)
from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/items/{item_id}")
async def get_item(
    item_id: int = Path(..., title="The ID of the item to get", ge=1) # ge = greater than or equal
):
    """
    ดึงข้อมูล Item โดยใช้ Item ID ที่ต้องเป็น Integer และมากกว่าหรือเท่ากับ 1
    """
    return {"item_id": item_id}

คำอธิบาย:

  • Path(...): ใช้สำหรับกำหนด Metadata และ Validation เพิ่มเติมให้กับ Path Parameter
  • ...: หมายถึง Required (จำเป็นต้องมี)
  • title, description, example: ใช้สำหรับเอกสาร Swagger UI
  • ge=1 (greater than or equal): กำหนดเงื่อนไขว่า item_id ต้องมีค่ามากกว่าหรือเท่ากับ 1

FastAPI จะทำการตรวจสอบความถูกต้องของค่า Path Parameter ตามเงื่อนไขเหล่านี้ให้โดยอัตโนมัติครับ

Query Parameters

Query Parameters ใช้สำหรับกรอง, จัดเรียง, หรือกำหนดตัวเลือกเพิ่มเติมในการดึงข้อมูล มักจะปรากฏหลังเครื่องหมาย ? ใน URL ครับ

# main.py (ตัวอย่าง)
from typing import Optional, List
from fastapi import Query

@app.get("/items/")
async def read_items(
    q: Optional[str] = Query(
        None,
        min_length=3,
        max_length=50,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        alias="item-query" # กำหนดชื่อ Query Parameter ใน URL เป็น 'item-query' แทน 'q'
    ),
    limit: int = Query(10, gt=0, le=100), # ค่า default คือ 10, ต้อง > 0 และ <= 100
    tags: List[str] = Query([]) # รับ Query Parameter หลายค่า เช่น /items/?tags=food&tags=drink
):
    """
    ดึงรายการ Item พร้อม Query Parameters สำหรับการค้นหา, จำกัดจำนวน, และกรองตาม Tags
    """
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    if limit:
        results.update({"limit": limit})
    if tags:
        results.update({"tags": tags})
    return results

คำอธิบาย:

  • Query(None, ...): ใช้สำหรับกำหนด Metadata และ Validation เพิ่มเติมให้กับ Query Parameter
  • min_length, max_length: กำหนดความยาวของ String
  • alias="item-query": ให้ Query Parameter ใน URL เป็น item-query แต่ในโค้ดเรายังคงใช้ชื่อ q ได้ครับ
  • List[str] = Query([]): แสดงถึง Query Parameter ที่สามารถรับค่าได้หลายครั้ง เช่น /items/?tags=tag1&tags=tag2

การใช้ Path และ Query ร่วมกัน

คุณสามารถใช้ Path Parameters และ Query Parameters ร่วมกันใน Path Operation เดียวกันได้ครับ

# main.py (ตัวอย่าง)
@app.get("/users/{user_id}/orders")
async def get_user_orders(
    user_id: int = Path(..., description="The ID of the user"),
    status: Optional[str] = Query(None, description="Filter orders by status")
):
    """
    ดึงรายการคำสั่งซื้อของ User ที่ระบุ โดยสามารถกรองตามสถานะได้
    """
    orders = [{"id": 1, "item": "Laptop", "status": "shipped"}, {"id": 2, "item": "Mouse", "status": "pending"}]
    
    if status:
        orders = [order for order in orders if order["status"] == status]
        
    return {"user_id": user_id, "orders": orders}

การใช้ Path และ Query Parameters อย่างเหมาะสมช่วยให้ API มีความยืดหยุ่นและชัดเจนในการใช้งานครับ

Dependency Injection: การจัดการการพึ่งพิง

Dependency Injection (DI) เป็นหนึ่งในคุณสมบัติที่ทรงพลังที่สุดของ FastAPI ที่ช่วยให้การจัดการโค้ดที่ซับซ้อน เช่น การเชื่อมต่อฐานข้อมูล การตรวจสอบสิทธิ์ หรือการนำเข้าบริการต่างๆ ทำได้อย่างเป็นระเบียบและง่ายต่อการทดสอบครับ

แนวคิดของ Dependency Injection

แนวคิดหลักคือแทนที่จะให้ฟังก์ชันหรือคลาสสร้าง Dependencies (สิ่งที่ต้องพึ่งพิง) ด้วยตัวเอง เราจะ "ฉีด" (Inject) Dependencies เหล่านั้นเข้าไปให้ครับ

สมมติว่าคุณมีฟังก์ชันที่ต้องการเชื่อมต่อฐานข้อมูล ทุกครั้งที่ฟังก์ชันถูกเรียก คุณอาจจะสร้าง Connection ใหม่ ซึ่งไม่เหมาะสมครับ แต่ถ้าเรามีฟังก์ชัน get_db_session() ที่สร้าง Connection ให้ และเราแค่บอกฟังก์ชันหลักว่า "ฉันต้องการ DB Session นะ" แล้ว FastAPI จะจัดการเรียก get_db_session() และส่งค่ากลับมาให้เอง นี่คือ Dependency Injection ครับ

การสร้างและใช้งาน Dependency

เราใช้ฟังก์ชัน Depends จาก fastapi เพื่อบอกว่า Argument ของ Path Operation Function เป็น Dependency ครับ

# main.py (ตัวอย่าง)
from fastapi import FastAPI, Depends, Header, HTTPException, status
from typing import Optional

app = FastAPI()

# Dependency: ตรวจสอบ Header ที่กำหนด
async def verify_token(x_token: str = Header(...)):
    """
    Dependency สำหรับตรวจสอบ Token ใน HTTP Header
    """
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid X-Token header")
    return x_token

# Dependency: รับ User ID (จำลอง)
async def get_current_user(token: str = Depends(verify_token)):
    """
    Dependency สำหรับดึงข้อมูล User จาก Token (จำลอง)
    """
    # ในโลกจริง จะถอดรหัส token เพื่อดึง user_id
    fake_users_db = {
        "fake-super-secret-token": {"username": "johndoe", "email": "[email protected]", "id": 1}
    }
    user = fake_users_db.get(token)
    if not user:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials")
    return user

@app.get("/users/me/")
async def read_users_me(current_user: dict = Depends(get_current_user)):
    """
    ดึงข้อมูล User ปัจจุบัน (ต้องผ่านการตรวจสอบ Token)
    """
    return current_user

@app.get("/items_protected/")
async def read_protected_items(current_user: dict = Depends(get_current_user)):
    """
    ดึงข้อมูล Item ที่ต้องมีการตรวจสอบสิทธิ์
    """
    return [
        {"item_id": "Foo", "owner": current_user["username"]},
        {"item_id": "Bar", "owner": current_user["username"]},
    ]

คำอธิบาย:

  • verify_token เป็น Dependency ที่ตรวจสอบ X-Token Header
  • get_current_user เป็น Dependency ที่ พึ่งพา verify_token อีกที (ใช้ token: str = Depends(verify_token)) และดึงข้อมูล User
  • read_users_me และ read_protected_items เป็น Path Operation ที่พึ่งพา get_current_user
  • FastAPI จะจัดการเรียก Dependencies ตามลำดับ และส่งค่าที่ได้กลับมาเป็น Argument ให้กับฟังก์ชันหลัก
  • ถ้า Dependency ใดๆ ยก HTTPException, Request จะหยุดลงและคืนค่า Error นั้นทันที

Dependency Injection ทำให้โค้ดโมดูลาร์ขึ้น, ทดสอบง่ายขึ้น, และนำกลับมาใช้ใหม่ได้ง่ายขึ้นมากๆ ครับ

การจัดการ Error และ Exception

การจัดการข้อผิดพลาด (Error Handling) เป็นสิ่งสำคัญในการสร้าง API ที่แข็งแกร่งและใช้งานง่ายครับ FastAPI มีกลไกในการจัดการ Exception ที่ชัดเจนและมีประสิทธิภาพ

HTTP Exceptions (Built-in)

FastAPI มีคลาส HTTPException ในตัวที่คุณสามารถใช้เพื่อส่ง HTTP Error Response ที่มี Status Code และ Detail Message ที่กำหนดเองได้ครับ

# main.py (ตัวอย่าง)
from fastapi import FastAPI, HTTPException, status

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    """
    ดึงข้อมูล User โดย user_id
    ถ้าไม่พบจะคืนค่า HTTP 404 Not Found
    """
    # สมมติว่าดึงมาจากฐานข้อมูล
    fake_users_db = {
        1: {"username": "johndoe"},
        2: {"username": "janedoe"},
    }
    user = fake_users_db.get(user_id)
    if user is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, # 404
            detail="User not found"
        )
    return user

@app.post("/items_limited/")
async def create_item_limited(name: str):
    """
    สร้าง Item แต่จำกัดความยาวชื่อ
    ถ้าชื่อยาวเกินไปจะคืนค่า HTTP 400 Bad Request
    """
    if len(name) > 10:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, # 400
            detail="Item name is too long (max 10 characters)"
        )
    return {"name": name, "status": "created"}

เมื่อ HTTPException ถูกยกขึ้น FastAPI จะสร้าง JSON Response ที่มี "detail" field และ HTTP Status Code ที่คุณระบุให้โดยอัตโนมัติครับ

Custom Exception Handlers

บางครั้งคุณอาจต้องการจัดการกับ Exception ชนิดอื่นที่ไม่ใช่ HTTPException (เช่น Database Connection Error, Custom Application Error) หรือต้องการปรับแต่งรูปแบบ Response ของ HTTPException เองครับ คุณสามารถทำได้โดยการสร้าง Custom Exception Handler

# main.py (ตัวอย่าง)
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()

# สร้าง Custom Exception
class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name

# สร้าง Exception Handler สำหรับ Custom Exception
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=status.HTTP_418_IM_A_TEAPOT, # ตัวอย่าง Status Code ที่ไม่ธรรมดา
        content={"message": f"Oops! {exc.name} did something wrong. It's a teapot."},
    )

# ตัวอย่างการ Handle HTTPException ที่เกิดจาก Validation Error (หรืออื่นๆ)
@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request: Request, exc: StarletteHTTPException):
    # คุณสามารถปรับแต่ง Response ของ HTTPException ได้ที่นี่
    return JSONResponse(
        status_code=exc.status_code,
        content={"error_type": "HTTP Error", "message": exc.detail},
    )

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

คำอธิบาย:

  • เราสร้างคลาส UnicornException ขึ้นมาเอง
  • เราใช้ @app.exception_handler(UnicornException) เพื่อลงทะเบียนฟังก์ชัน unicorn_exception_handler ให้เป็นตัวจัดการเมื่อ UnicornException ถูกยกขึ้น
  • request: Request: Argument นี้ให้คุณเข้าถึงข้อมูล Request ได้
  • exc: UnicornException: Argument นี้คือ Instance ของ Exception ที่ถูกยกขึ้น
  • เราคืนค่า JSONResponse ที่มี Status Code และ Content ที่เราต้องการ
  • สำหรับ StarletteHTTPException (ซึ่งเป็นคลาสแม่ของ FastAPI HTTPException) เราสามารถใช้ Handler เพื่อปรับแต่ง Default Error Response ได้ครับ

การจัดการ Exception ที่ดีช่วยให้ API ของคุณมีความทนทานและให้ข้อมูลที่เป็นประโยชน์แก่ Client เมื่อเกิดข้อผิดพลาดครับ

การเชื่อมต่อฐานข้อมูล

API ส่วนใหญ่ไม่ได้ทำงานอยู่โดดๆ แต่ต้องมีการจัดเก็บและดึงข้อมูลจากฐานข้อมูลครับ ในส่วนนี้ เราจะมาดูวิธีการเชื่อมต่อ FastAPI กับฐานข้อมูล โดยจะใช้ SQLAlchemy ซึ่งเป็น ORM (Object-Relational Mapper) ยอดนิยมของ Python และ SQLite เป็นฐานข้อมูลตัวอย่างครับ

เลือกฐานข้อมูล

ก่อนอื่นต้องเลือกฐานข้อมูลที่เหมาะสมกับโปรเจกต์ของคุณครับ

  • SQL Databases (Relational Databases): เช่น PostgreSQL, MySQL, SQLite, SQL Server เหมาะสำหรับข้อมูลที่มีโครงสร้างชัดเจนและต้องการความสัมพันธ์ที่ซับซ้อน
  • NoSQL Databases (Non-relational Databases): เช่น MongoDB, Cassandra, Redis เหมาะสำหรับข้อมูลที่ไม่มีโครงสร้างตายตัว หรือต้องการความเร็วในการอ่าน/เขียนสูง

ในตัวอย่างนี้ เราจะใช้ SQLite ซึ่งเป็นฐานข้อมูลแบบไฟล์ ไม่ต้องติดตั้ง Server เพิ่มเติม เหมาะสำหรับการเริ่มต้นและการพัฒนาครับ

ตัวอย่างการเชื่อมต่อกับ SQLAlchemy และ SQLite

เราจะจัดโครงสร้างโปรเจกต์ให้เป็นระเบียบมากขึ้นเพื่อรองรับการทำงานกับฐานข้อมูลครับ

my-fastapi-app/
├── venv/
├── main.py
├── database.py       # ตั้งค่าการเชื่อมต่อฐานข้อมูล
├── models.py         # กำหนด SQLAlchemy Models (โครงสร้างตาราง)
├── schemas.py        # กำหนด Pydantic Models (โครงสร้างข้อมูลสำหรับ API)
└── crud.py           # ฟังก์ชันสำหรับ Create, Read, Update, Delete (CRUD)

ก่อนอื่น ติดตั้งไลบรารีที่จำเป็นครับ:

pip install sqlalchemy "pydantic[email]" # pydantic[email] สำหรับ email validation
# เราไม่ได้ใช้ database driver เพิ่มเติมสำหรับ SQLite เพราะเป็น built-in ของ Python

ไฟล์ schemas.py (Pydantic Models)

ไฟล์นี้จะเก็บ Pydantic Models ที่เราใช้สำหรับ Request Body และ Response Model แยกออกจาก SQLAlchemy Models เพื่อความชัดเจนและยืดหยุ่น

# schemas.py
from typing import List, Optional
from pydantic import BaseModel, EmailStr

class ItemBase(BaseModel):
    title: str
    description: Optional[str] = None

class ItemCreate(ItemBase):
    pass

class Item(ItemBase):
    id: int
    owner_id: int

    class Config:
        orm_mode = True # สำคัญมาก! สำหรับการแปลง SQLAlchemy Object เป็น Pydantic Model

class UserBase(BaseModel):
    email: EmailStr

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int
    is_active: bool
    items: List[Item] = [] # User มี List ของ Item

    class Config:
        orm_mode = True

ไฟล์ database.py

ไฟล์นี้จะจัดการการเชื่อมต่อกับฐานข้อมูลและสร้าง Session ครับ

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# URL สำหรับเชื่อมต่อ SQLite (จะสร้างไฟล์ database.db ในโฟลเดอร์เดียวกัน)
SQLALCHEMY_DATABASE_URL = "sqlite:///./database.db"

# สร้าง SQLAlchemy Engine
# connect_args={"check_same_thread": False} จำเป็นสำหรับ SQLite เพื่อให้ทำงานกับหลาย Request พร้อมกันได้
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

# สร้าง SessionLocal class สำหรับการสร้าง Database Session
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Base class ที่ SQLAlchemy Models จะสืบทอดไป
Base = declarative_base()

# Dependency สำหรับการสร้างและปิด Database Session
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

ไฟล์ models.py (SQLAlchemy Models)

ไฟล์นี้จะกำหนดโครงสร้างของตารางในฐานข้อมูลโดยใช้ SQLAlchemy ครับ

# models.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship

from .database import Base # นำเข้า Base จาก database.py

class User(Base):
__tablename__ = "users" # ชื่อตารางในฐานข้อมูล

id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)

# ความสัมพันธ์กับ Item (One-to-Many: User หนึ่งคนมี Item ได้หลายชิ้น)
items = relationship("Item", back_populates="owner")

class Item(Base):
__tablename__ = "items" # ชื่อตารางในฐานข้อมูล

id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id")) # Foreign Key

# ความสัมพันธ์กับ User (Many-to-One: Item หนึ่งชิ้นมี Owner เป็น User หนึ่งคน)
owner = relationship("User", back

จัดส่งรวดเร็วส่งด่วนทั่วประเทศ
รับประกันสินค้าเคลมง่าย มีใบรับประกัน
ผ่อนชำระได้บัตรเครดิต 0% สูงสุด 10 เดือน
สะสมแต้ม รับส่วนลดส่วนลดและคะแนนสะสม

© 2026 SiamLancard — จำหน่ายการ์ดแลน อุปกรณ์ Server และเครื่องพิมพ์ใบเสร็จ

SiamLancard
Logo
Free Forex EA Download — XM Signal · EA Forex ฟรี
iCafeForex.com - สอนเทรด Forex | SiamCafe.net
Shopping cart