รู้จักกับ FastAPI

/ 15 min read

Share on social media

fastapi-python สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video

FastAPI คืออะไร

https://fastapi.tiangolo.com/

FastAPI เป็น Framework ที่ใช้สำหรับการพัฒนา web application ด้วย Python โดยเน้นไปที่การสร้าง API ที่ “รวดเร็ว” ปลอดภัย และมีประสิทธิภาพสูง โดย FastAPI ถูกออกแบบมาให้ใช้ “งานง่าย” มีความยืดหยุ่นสูง และสามารถประยุกต์ใช้กับ project ขนาดใหญ่ได้

จุดเด่นของ FastAPI

  1. ประสิทธิภาพสูง: FastAPI มีประสิทธิภาพสูงและเร็วมากเนื่องจากถูกพัฒนาขึ้นโดยใช้ Starlette และ Pydantic ซึ่งทำให้สามารถจัดการกับการร้องขอหลาย ๆ คำขอพร้อมกันได้อย่างมีประสิทธิภาพ
  2. รองรับ Asynchronous: FastAPI รองรับการเขียนโค้ดแบบ asynchronous ซึ่งทำให้สามารถจัดการกับ I/O-bound tasks ได้ดีกว่า และเพิ่มประสิทธิภาพของระบบในการประมวลผลงานที่ใช้เวลาในการรอการตอบสนอง
  3. ตรวจสอบข้อมูลอัตโนมัติ: FastAPI มีระบบตรวจสอบข้อมูลอัตโนมัติจากโครงสร้างข้อมูลที่กำหนดใน Pydantic ซึ่งทำให้การตรวจสอบความถูกต้องของข้อมูลเป็นเรื่องง่ายและปลอดภัย
  4. เอกสาร API อัตโนมัติ: เมื่อสร้าง API ด้วย FastAPI จะมีการสร้างเอกสาร API (Swagger UI และ Redoc) อัตโนมัติ โดยไม่ต้องทำอะไรเพิ่มเติม ซึ่งช่วยให้การทดสอบและการพัฒนา API ง่ายขึ้นมาก
  5. รองรับ OpenAPI และ JSON Schema: FastAPI สร้างและตรวจสอบข้อมูลตามมาตรฐาน OpenAPI และ JSON Schema ซึ่งช่วยให้ API ของคุณสามารถทำงานร่วมกับเครื่องมือและบริการอื่น ๆ ที่รองรับมาตรฐานนี้ได้อย่างราบรื่น
  6. พัฒนารวดเร็ว: FastAPI ออกแบบมาให้ใช้เวลาในการพัฒนาน้อยลง มีการใช้ Type Hints ของ Python เพื่อเพิ่มความชัดเจนของโค้ด และลดข้อผิดพลาดที่อาจเกิดขึ้นในระหว่างการพัฒนา

เพิ่มเติม library ที่ FastAPI ใช้ Starlette และ Pydantic

  • Starlette (https://www.starlette.io/) เป็น library ที่เน้นการสร้าง web application ที่มีประสิทธิภาพสูงและสามารถจัดการกับงานที่ต้องใช้การประมวลผลแบบ asynchronous ได้ดี เป็น ASGI (Asynchronous Server Gateway Interface) framework ที่ใช้ในการสร้าง backend services ขนาดเล็กหรือ microservices รวมถึง API ที่มีการทำงานที่เร็วและเบา โดยคุณสมบัติเด่นของ Starlette คือ:
    • การจัดการกับ asynchronous tasks: Starlette ถูกออกแบบมาให้รองรับ asynchronous I/O โดยใช้ asyncio ของ Python ทำให้สามารถจัดการกับคำขอที่เข้ามาพร้อมกันหลาย ๆ คำขอได้อย่างมีประสิทธิภาพ
    • Routing: Starlette มีระบบการกำหนดเส้นทาง (routing) ที่ยืดหยุ่นและง่ายต่อการใช้งาน
    • Middleware: รองรับการเพิ่ม middleware ซึ่งช่วยให้สามารถปรับแต่งการทำงานของแอปพลิเคชันได้ง่าย
    • Session และ Cookie: มีระบบจัดการ session และ cookie ในตัวที่สามารถใช้งานได้ทันที
  • Pydantic (https://docs.pydantic.dev/latest/) เป็น library สำหรับการตรวจสอบและจัดการข้อมูลใน Python โดยเน้นไปที่การทำงานกับโครงสร้างข้อมูลที่มีความซับซ้อน และมีคุณสมบัติที่สำคัญดังนี้:
    • การตรวจสอบข้อมูลอัตโนมัติ: Pydantic ใช้ type hints ของ Python ในการตรวจสอบความถูกต้องของข้อมูล (data validation) โดยอัตโนมัติ เช่น การตรวจสอบชนิดของข้อมูล ความยาวของข้อมูล เป็นต้น
    • การแปลงข้อมูลอัตโนมัติ: Pydantic สามารถแปลงข้อมูลจากชนิดหนึ่งไปยังอีกชนิดหนึ่งได้อย่างอัตโนมัติ เช่น การแปลง string เป็น integer ถ้าเป็นไปได้
    • การสร้าง schema จากโครงสร้างข้อมูล: Pydantic สามารถสร้าง JSON Schema จากโมเดลที่กำหนด ซึ่งช่วยให้การทำงานกับ API หรือการตรวจสอบความถูกต้องของข้อมูลในระบบที่ใหญ่ขึ้นง่ายดายขึ้น
    • การจัดการค่าดีฟอลต์และค่าเพิ่มเติม: Pydantic รองรับการกำหนดค่าเริ่มต้นและการจัดการค่าที่ไม่ตรงกับโครงสร้างข้อมูลที่กำหนด

ถ้าเล่ากันแบบง่ายๆ FastAPI ยืนอยู่บนบ่าของยักษ์ใหญ่ โดย

  • Starlette สำหรับส่วนที่เกี่ยวกับเว็บ
  • Pydantic สำหรับส่วนที่เกี่ยวกับข้อมูล

มาลองเล่น FastAPI กัน

มาเริ่มต้นกับ FastAPI กัน

คำแนะนำของ FastAPI นั้นจริงๆ สามารถสร้าง virtual environment ของ python ก่อน เพื่อเป็นการแยก environment ในแต่ละ project ออกจากกันตามคำแนะนำในนี้ได้
https://fastapi.tiangolo.com/virtual-environments/
(สำหรับใครที่เป็นสาย python จริงจัง หรือ พัฒนา project จริงจังแนะนำให้ทำ เพื่อกัน library / package ชนกันใน python แต่ละ version)

แต่ทีนี้เพื่อให้เกิดความรวดเร็วในการเล่า จะขออนุญาต skip ขั้นตอนนี้ไป และใช้ python ที่อยู่บนเครื่องแทน โดย fastapi สามารถลงโดยใช้คำสั่ง pip ในการลงได้เลย ด้วยคำสั่งนี้

Terminal window
pip install "fastapi[standard]"

pip (https://pypi.org/project/pip/) เป็นเครื่องมือจัดการ package สำหรับ Python ที่ใช้ในการติดตั้ง upgrade และจัดการ library หรือ package ต่าง ๆ ที่พัฒนาโดยชุมชน Python (ถ้าใครที่เป็นสาย javascript มา ให้อารมณ์เหมือนกับ npm นั่นแหละ)
pip ช่วยให้ผู้ใช้สามารถติดตั้ง package จาก Python Package Index (PyPI) ซึ่งเป็นที่เก็บแพ็กเกจหลักของ Python ได้อย่างง่ายดาย โดยเพียงแค่ใช้คำสั่ง pip install ตามด้วยชื่อ package ที่ต้องการ นอกจากนี้ pip ยังสามารถใช้ในการจัดการเวอร์ชันของ pacakge ตรวจสอบความเข้ากันได้ของ library และถอนการติดตั้ง package ที่ไม่ต้องการได้เช่นกัน

หลังจากลงเสร็จ ให้เราสร้าง folder สักอันขึ้นมา และสร้างไฟล์ main.py เพื่อลองสร้าง Example API สำหรับลอง run FastAPI ขึ้นมา เราจะลองลอกตัวอย่างจาก Fast API มาเลย

from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}

ลองสร้างไฟล์ตาม code นี้ (ในทีนี้เราจะใช้ vscode ในการ run) และทำการ run fastapi ด้วยคำสั่งนี้บน terminal ของ vscode

Terminal window
fastapi dev main.py
fast-python-01.webp

ผลลัพธ์สุดท้าย ถ้าทุกอย่างทุกต้อง จะ run อยู่ที่ http://127.0.0.1:8000 ออกมา

fast-python-02.webp

เมื่อลองยิง API (ในทีนี้ เราจะใช้ Postman) ดูที่ root path ก็จะเจอว่าสามารถยิง API และได้ผลลัพธ์ออกมาเป็น Hello World ตาม code ออกมาได้

fast-python-03.webp

ความว้าวซ่าของ FastAPI คือ FastAPI ได้เตรียม Interactive API docs เอาไว้ให้แล้ว 2 เจ้า และ built in มาแล้วเรียบร้อย !

เจ้าแรกคือ Swagger (https://swagger.io/) เราสามารถเปิด docs swagger ดูได้ทันทีผ่าน http://127.0.0.1:8000/docs ใช่ครับ ไม่ต้องลงอะไรเพิ่มเลย มี built in มาแล้วเรียบร้อย หมดปัญหาทำงานแล้วไม่มี document ไปได้เลย 😂
fast-python-04.webp

หรืออีกเจ้า (บางคนอาจจะไม่ถนัด Swagger) FastAPI ก็ได้ support ReDoc (https://github.com/Redocly/redoc) API Docs จาก OpenAPI เอาไว้ด้วยเช่นกัน มี built in มาแล้วเรียบร้อย สามารถเปิดได้ผ่าน path http://127.0.0.1:8000/redoc ได้เลยเช่นกัน

fast-python-05.webp

ซึ่งเดี๋ยวระหว่างตัวอย่าง เราจะมีการเพิ่มการใช้ Type เข้าไป จะเห็นเลยว่า document พวกนี้จะล้อตาม code ของ FastAPI ไปเช่นกัน ซึ่งถือว่าอำนวยความสะดวกในการทำ document ให้อย่างมากด้วยเช่นกัน

รวมถึง ทุกครั้งที่แก้ code เราจะสังเกตุว่า เราไม่จำเป็นต้อง run คำสั่ง fastapi run dev ใหม่ทุกรอบ เนื่องจาก FastAPI ได้เตรียม auto-reload ซึ่งถูกจัดการโดย Uvicorn (https://www.uvicorn.org/) เมื่อ run FastAPI ใน development mode จะทำการเปิดใช้ feature นี้อัตโนมัติ ทำให้เราสามารถพัฒนา application ได้ โดยไม่จำเป็นต้องคอยมา run ซ้ำๆทุกครั้งที่แก้ไขไฟล์ได้

ทีนี้ เรามาเรียนรู้พื้นฐานการทำ RestAPI ผ่าน FastAPI กัน

ตัวอย่าง CRUD

REST API คือ interface ที่ใช้ในการสื่อสารระหว่างระบบต่าง ๆ ผ่าน protocal HTTP โดยอิงตามหลักการของสถาปัตยกรรม REST (Representational State Transfer) REST API ใช้ในการสร้างบริการเว็บที่สามารถให้ข้อมูลหรือดำเนินการต่าง ๆ ตามคำขอจาก client (เช่น เว็บ browsser หรือ application) ไปยัง server โดยข้อมูลหรือ resource จะถูกระบุด้วย URL และสามารถดำเนินการผ่าน HTTP methods เช่น

  • GET (ดึงข้อมูล)
  • POST (สร้างข้อมูล)
  • PUT (อัปเดตข้อมูล)
  • DELETE (ลบข้อมูล)

ใน FastAPI การใช้งาน HTTP methods พื้นฐาน สามารถทำได้โดยใช้ decorators ที่มีให้ภายใน framework นี้ โดย decorators เหล่านี้จะช่วยกำหนดว่าฟังก์ชันใน Python ใดจะตอบสนองต่อ HTTP method ใดบ้าง โดยทั่วไปแล้ว FastAPI มีการสนับสนุน HTTP methods หลักๆดังนี้

1. GET

from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def read_root():
return {"message": "Hello, World!"}
  • ใน FastAPI จะใช้ decorators @app.<method> ในการควบคุม HTTP Method ของ FastAPI เช่น @app.get สำหรับ method GET

  • จากตัวอย่างนี้ function read_root จะถูกเรียกใช้เมื่อผู้ใช้ทำการส่งคำขอ GET ไปยัง endpoint /hello ออกมาได้
    fast-python-06.webp

2. POST

ตัวอย่างแบบไม่ใช้ Pydantic

from fastapi import FastAPI, Request
import json
app = FastAPI()
@app.post("/submit/")
async def submit_data(request: Request):
body = await request.json()
# Now you can access the data in `body` dictionary
return {"received_data": body}

แบบใช้ Pydantic

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
@app.post("/items/")
def create_item(item: Item):
return {"item_name": item.name, "item_description": item.description}
  • ใช้ผ่าน decorator @app.post ในการควบคุม method POST
  • function create_item จะถูกเรียกใช้เมื่อผู้ใช้ทำการส่งคำขอ POST ไปยัง endpoint /items/ พร้อมกับข้อมูลของ Item
  • สังเกตุว่าในเคสนี้เรามีการเรียกใช้ pydantic สำหรับจัดการ type เพิ่ม

Pydantic เป็น library ใน Python ที่ช่วยในการจัดการข้อมูลและการตรวจสอบความถูกต้องของข้อมูล (data validation) โดยใช้ type annotations ของ Python ในการกำหนดโครงสร้างของข้อมูลที่ expected ว่าจะเข้ามา เช่น การกำหนดชนิดข้อมูลและการตรวจสอบข้อกำหนดต่าง ๆ ข้อมูลต้องไม่ว่างเปล่า ต้องเป็นตัวเลขอยู่ในช่วงที่กำหนด เป็นต้น

Pydantic มีบทบาทสำคัญในหลายๆ ด้าน ตั้งแต่

  • Data Models: Pydantic ช่วยให้เราสร้าง model ข้อมูลที่ใช้ในการรับส่งข้อมูลระหว่าง client และ server โดยการกำหนด Pydantic models เพื่อจัดการข้อมูลที่รับจาก request body, query parameters, headers หรือ cookies และตรวจสอบความถูกต้องของข้อมูลได้อย่างอัตโนมัติ
  • Validation and Serialization: เมื่อรับข้อมูลที่ถูกส่งมาจาก client, FastAPI จะใช้ Pydantic ในการตรวจสอบความถูกต้องของข้อมูลเหล่านั้น เช่น ตรวจสอบว่าข้อมูลที่รับมาตรงกับชนิดข้อมูลที่กำหนดไว้หรือไม่ หรือมีค่าที่จำเป็นครบถ้วนหรือไม่ และเมื่อส่งข้อมูลกลับไปยัง client, Pydantic จะช่วยในการทำ serialization (แปลงข้อมูลให้อยู่ในรูปแบบที่สามารถส่งกลับไปได้)
  • Automatic Documentation: เมื่อใช้ Pydantic models ใน FastAPI, การสร้างเอกสาร API (เช่น Swagger UI) จะเกิดขึ้นอัตโนมัติ ซึ่งช่วยให้ผู้พัฒนาหรือผู้ใช้ API เข้าใจโครงสร้างข้อมูลที่ API คาดหวังได้ง่ายขึ้น
  • Custom Validators: Pydantic ยังรองรับการสร้างตัวตรวจสอบความถูกต้องของข้อมูลที่ซับซ้อน (custom validators) ซึ่งเราสามารถใช้ในการปรับแต่งการตรวจสอบข้อมูลให้ตรงกับความต้องการของแอปพลิเคชันของเรา

โดยทั่วไป การใช้ Pydantic มักใช้คู่กับการจัดการ Data เพื่อให้ Data ยังคงอยู่ในรูปแบบเดียวกัน รวมถึง ใช้ร่วมกันกับ ORM (ซึ่งเดี๋ยวเราจะพูดถึงอีกที) เพื่อให้มี data type ที่ถูกต้องก่อนเข้า database ด้วย

3. PUT

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_id": item_id, "item_name": item.name, "item_description": item.description}
  • ใช้ผ่าน decorator @app.put ในการควบคุม method PUT

  • function update_item จะถูกเรียกใช้เมื่อผู้ใช้ทำการส่งคำขอ PUT ไปยัง endpoint /items/{item_id} พร้อมกับข้อมูลใหม่สำหรับ Item

4. DELETE

from fastapi import FastAPI
app = FastAPI()
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
return {"message": f"Item {item_id} deleted"}
  • ใช้ผ่าน decorator @app.delete ในการควบคุม method DELETE

  • function delete_item จะถูกเรียกใช้เมื่อผู้ใช้ทำการส่งคำขอ DELETE ไปยัง endpoint /items/{item_id} เพื่อทำการลบข้อมูลตาม item_id ที่ระบุ

5. PATCH

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
@app.patch("/items/{item_id}")
def partial_update_item(item_id: int, item: Item):
return {"item_id": item_id, "item_name": item.name, "item_description": item.description}
  • ใช้ผ่าน decorator @app.patch ในการควบคุม method PATCH
  • function partial_update_item จะถูกเรียกใช้เมื่อผู้ใช้ทำการส่งคำขอ PATCH ไปยัง endpoint /items/{item_id} เพื่อทำการอัปเดตข้อมูลบางส่วนของ Item

ทีนี้สังเกตุว่า ตัวอย่างที่เราโชว์ทั้งหมดนั้น เป็นตัวอย่างที่เราทำเป็น Mock data ไว้อย่างเดียว (จัดการข้อมูลผ่าน List, แสดงข้อมูลหยิบจาก List ออกไปแสดง) เราจะมาลองเปลี่ยน List เหล่านั้นให้ต่อ database จริงกันด้วย library ของ FastAPI กัน

เพิ่ม Database เข้าไป

https://fastapi.tiangolo.com/tutorial/sql-databases/
มีประโยคหนึ่งจากเอกสารของ FastAPI

  • FastAPI doesn’t require you to use a SQL (relational) database.
  • But you can use any relational database that you want

นั่นคือ FastAPI สามารถใช้งาน Relational Database ได้ โดยไม่ใช้ภาษา SQL ซึ่งคำตอบสำหรับเรื่องราวนี้ที่จะเป็นไปได้ก็มีเพียงคำตอบเดียว นั่นคือการใช้ ORM (Object Relational Mapper) ของ https://www.sqlalchemy.org/ ในการเป็นตัวแทนของการพูดคุยกับ Database นั่นเอง

FastAPI สามารถทำงานร่วมกับฐานข้อมูลใดก็ได้และใช้ library แบบใดก็ได้ในการเชื่อมต่อกับฐานข้อมูล รูปแบบที่พบบ่อยคือการใช้ “ORM” หรือ “object-relational mapping” library

ORM มีเครื่องมือในการแปลง (“map”) ระหว่าง objects ใน code และตารางฐานข้อมูล (“relations”) เมื่อใช้ ORM ปกติแล้วคุณจะสร้าง class ที่แทนตารางในฐานข้อมูล SQL โดยแต่ละ attribute ของ class จะแทน column ซึ่งมีชื่อและประเภทกำหนดไว้

ORMs ที่พบบ่อยใน python ก็จะมี เช่น Django-ORM (เป็นส่วนหนึ่งของ Django framework), SQLAlchemy ORM (เป็นส่วนหนึ่งของ SQLAlchemy, ไม่ขึ้นกับ framework ใด ๆ) และ Peewee (ไม่ขึ้นกับ framework ใด ๆ) เป็นต้น

SQLAlchemy เป็น library ในภาษา Python ที่ถูกออกแบบมาเพื่อจัดการกับ relational databases โดยทำหน้าที่เป็น ORM และยังมีความสามารถในการทำ SQL expression language ซึ่งช่วยให้เราสามารถทำงานกับฐานข้อมูลได้ทั้งในแบบ low-level (การเขียน SQL query โดยตรง) และ high-level (การใช้ ORM เพื่อ map ข้อมูลจากตารางในฐานข้อมูลมาเป็น objects ใน Python) ได้
ความสามารถหลักของ SQLAlchemy

  1. ORM (Object-Relational Mapping): SQLAlchemy ORM ช่วยให้ผู้พัฒนาสามารถทำงานกับฐานข้อมูลโดยใช้ class และ objects ใน Python แทนการเขียน SQL query โดยตรง ตัวอย่างเช่น ตารางในฐานข้อมูลจะถูกแทนที่ด้วย class และแต่ละ row ในตารางจะถูกแทนที่ด้วย instance ของ class นั้นๆ ซึ่งทำให้การทำงานกับข้อมูลในฐานข้อมูลเป็นไปอย่างธรรมชาติและเป็นไปตามแนวคิดของการเขียนโปรแกรม OOP ได้
  2. SQL Expression Language: SQLAlchemy ยังมีเครื่องมือที่ช่วยให้เราสามารถเขียน SQL query ได้ในรูปแบบที่เป็น programmatic (ไม่ใช่ SQL string โดยตรง) ทำให้การสร้างและจัดการ query เป็นไปอย่างยืดหยุ่น และยังสามารถใช้ฟีเจอร์นี้ร่วมกับ ORM ได้อีกด้วย
  3. Database Abstraction: SQLAlchemy สามารถทำงานกับหลายประเภทของฐานข้อมูลเชิงสัมพันธ์ได้ เช่น MySQL, PostgreSQL, SQLite, Oracle เป็นต้น โดยผู้พัฒนาไม่จำเป็นต้องกังวลเกี่ยวกับรายละเอียดเฉพาะของฐานข้อมูลแต่ละประเภท เพราะ SQLAlchemy จะทำหน้าที่เป็น layer ที่จัดการความซับซ้อนเหล่านี้
  4. Session Management: SQLAlchemy มีระบบจัดการ session ที่ช่วยในการทำงานกับ transactions ในฐานข้อมูล ซึ่ง session ช่วยในการควบคุมการเชื่อมต่อกับฐานข้อมูล และทำให้การ commit หรือ rollback ข้อมูลเป็นไปได้ง่ายขึ้น

เราจะมาลองเล่น sqlalchemy กัน โดยการลงตาม command นี้

Terminal window
pip install sqlalchemy

ลองปรับ code ให้ใช้ SQL Alchemy จาก CRUD ที่ทำมาโดย

  • ตัวอย่างนี้ เราจะใช้ SQLite สำหรับเก็บข้อมูลภายใน database
  • เราจะใช้ pydantic + sqlalchemy ในการจัดการ database ผ่าน ORM กัน

ทีนี้ เราจะเล่าจุดเริ่มต้นก่อน เราจะมีทั้งหมด 4 Step ที่เราต้องทำคือ

  • Step 1: Database SQLAlchemy Config
  • Step 2: สร้าง Database model
  • Step 3: สร้าง Pydantic model
  • Step 4: ประยุกต์ใช้กับ CRUD Service

Step 1 = Database ORM Config

ใน SQLAlchemy เมื่อทำงานกับ ORM โดยเฉพาะเมื่อใช้ใน application ที่ใช้ web framework อย่าง FastAPI จะต้องมีองค์ประกอบ 3 อย่างนี้เป็นจุดเริ่มต้น เพื่อใช้เป็นจุดเริ่มต้นระหว่าง python และ database (ORM) นั่นคือ engine, SessionLocal และ Base

  • engine คือการเชื่อมต่อกับฐานข้อมูล (database connection) ที่ SQLAlchemy ใช้ในการสื่อสารกับฐานข้อมูลจริงๆ โดยใช้ข้อมูลการเชื่อมต่อที่ระบุ เช่น ที่อยู่ของฐานข้อมูล, ชื่อผู้ใช้, รหัสผ่าน, และอื่น
  • SessionLocal class คือ factory function ที่ใช้ในการสร้าง session object ซึ่ง session นี้จะเป็นตัวจัดการ transaction (การเพิ่ม, ลบ, แก้ไข) ระหว่าง Python object และฐานข้อมูล
    • SessionLocal มักถูกสร้างขึ้นด้วย sessionmaker() โดยเชื่อมต่อกับ engine ที่สร้างไว้
  • Base class เป็น class หลักที่ใช้เป็น instance สำหรับการสร้าง class ที่แทนตารางใน database
    • class นี้จะถูกสร้างจาก declarative_base() ซึ่งจะทำให้ class อื่นๆ ที่สืบทอด (inheritance) สามารถแปลงเป็นตารางในฐานข้อมูลได้โดยอัตโนมัติได้

code สำหรับ config แรกสุดของ SQLAlchemy ก็จะมีหน้าตาประมาณนี้

# เพิิ่ม sqlalchemy เข้าไป
from sqlalchemy import create_engine, Column, Integer, String, Float, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# ตั้งค่าและเชื่อมต่อกับฐานข้อมูล SQLite
DATABASE_URL = "sqlite:///./test.db"
# instance class เริ่มต้นสำหรับ engine, SessionLocal และ Base
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

Step 2: สร้าง Database model (SQLAlchemy Model)

SQLAlchemy Models คือ class ใน Python ที่แทนตาราง (table) ในฐานข้อมูลโดยใช้ SQLAlchemy ORM Model เหล่านี้เป็นการเชื่อมต่อระหว่างโลกของ relational database และ OOP ใน Python
คุณสมบัติของ SQLAlchemy Models

  • Mapping ระหว่างตารางและ class: แต่ละ class ที่เป็น model ใน SQLAlchemy จะถูกแทนด้วยตารางในฐานข้อมูล และแต่ละ attribute ของคลาสจะถูกแทนด้วย column ในตารางนั้นๆ
  • Primary Key: ใน model แต่ละตัวจะมีการกำหนด Primary Key เพื่อให้สามารถ reference ถึงแต่ละ row ในตารางได้อย่างถูกต้อง
  • Data Types: SQLAlchemy จะกำหนดชนิดของข้อมูลสำหรับแต่ละ column ในตาราง เช่น Integer, String, DateTime เป็นต้น
  • Relationships: คุณสามารถกำหนดความสัมพันธ์ (relationship) ระหว่างตารางในฐานข้อมูลผ่านโมเดล เช่น One-to-Many, Many-to-One หรือ Many-to-Many

โดยการสร้าง SQLAlchemy models นั้น สามารถทำได้จากการใช้ Base class เป็นการกำหนดโครงสร้างของตารางในฐานข้อมูลโดยใช้ Python classes ที่สืบทอดมาจาก Base ซึ่งถูกสร้างขึ้นจาก declarative_base() (ใน Step ที่ 1 ก่อนหน้านี้)
ขั้นตอนที่เราจะต้องทำคือ

  1. import library สำหรับจัดการ attribute ในแต่ละ column (เพื่อกำหนดว่า column แต่ละอันคือ field ประเภทอะไรบ้าง) อย่าง Column, Integer, String, Boolean
  2. ใช้ declarative_base() เพื่อสร้าง Base class ที่จะใช้ในการสร้าง models
  3. สร้าง Model class โดยสืบทอดจาก **Base**:
    • กำหนดชื่อ class ตามชื่อที่ต้องการให้เป็นตารางในฐานข้อมูล
    • ระบุ __tablename__ ในแต่ละ class ซึ่งจะกำหนดชื่อตารางในฐานข้อมูล
    • กำหนดคอลัมน์ต่าง ๆ ในตารางโดยใช้ Column และกำหนดประเภทข้อมูล ( Integer, String, ฯลฯ)
  4. กำหนด Primary Key โดย ต้องมี Column อย่างน้อยหนึ่ง Column ที่ถูกกำหนดเป็น Primary Key ( primary_key=True)
  5. เมื่อ define ทุกอย่างเรียบร้อย run คำสั่ง Base.metadata.create_all(bind=engine) เพื่อทำการสร้าง table ทั้งหมดที่ define ขึ้นมา (โดยจะสร้างตารางในฐานข้อมูลจาก models ที่สืบทอดจาก Base class)
    ตัวอย่าง code ก็จะมีหน้าตาประมาณนี้ในส่วนที่ 2 (code ถัดจาก Step 1 ลงมา)
from pydantic import BaseModel
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# สร้างโมเดลสำหรับฐานข้อมูล
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(Text, nullable=True)
price = Column(Float, nullable=False)
tax = Column(Float, nullable=True)
# สร้างฐานข้อมูล
Base.metadata.create_all(bind=engine)

เพิ่มเติม ตัว ORM เองนั้น support การทำ relationship ระหว่าง table ด้วย (เช่น One-to-Many, Many-to-Many) โดยสามารถทำได้ผ่านคำสั่ง relationship
สามารถอ่านเพิ่มเติมในหัวข้อนี้ได้ https://fastapi.tiangolo.com/tutorial/sql-databases/#create-the-relationships
(ขอไม่ลง detail ในหัวข้อนี้เพื่อไม่ให้ยาวจนเกินไป + ให้เกิดความเข้าใจในตัว SQLAlchemy ก่อน)

Step 3: สร้าง Pydantic model

การสร้าง Pydantic Models เป็นกระบวนการในการสร้างตัวแบบข้อมูล (data model) โดยใช้ Pydantic ซึ่งเป็น library Python ที่ใช้สำหรับการตรวจสอบและแปลงข้อมูล (data validation and parsing) โดยเฉพาะอย่างยิ่งในบริบทของการทำงานร่วมกับ FastAPI และ SQLAlchemy การสร้าง Pydantic Models มีความสำคัญเนื่องจากช่วยในการจัดการข้อมูลขาเข้าและขาออกอย่างปลอดภัยและถูกต้อง
เมื่อใช้ SQLAlchemy ในการจัดการฐานข้อมูล Pydantic Models จะทำหน้าที่เป็นตัวกลางระหว่างข้อมูลที่มาจากฐานข้อมูลและข้อมูลที่ส่งไปยัง API หรือได้รับจาก API โดยมีจุดประสงค์ดังนี้:

  1. การตรวจสอบและแปลงข้อมูล: Pydantic ช่วยตรวจสอบความถูกต้องของข้อมูลที่ได้รับจากการร้องขอ (request) เช่น ข้อมูลจากผู้ใช้ หรือข้อมูลที่ส่งกลับไปยังผู้ใช้ (response) ซึ่งช่วยป้องกันข้อผิดพลาดที่อาจเกิดจากข้อมูลที่ไม่ถูกต้อง
  2. การแปลงระหว่าง SQLAlchemy Models และ Pydantic Models: ในการทำงานกับ API, ข้อมูลที่ถูกเก็บในฐานข้อมูล (ซึ่งใช้ SQLAlchemy Models) มักจะต้องถูกแปลงเป็น Pydantic Models เพื่อให้ส่งกลับไปยังผู้ใช้ในรูปแบบที่เหมาะสมและตรวจสอบแล้ว เช่น ข้อมูล JSON

ในเอกสารของ FastAPI เองเพื่อกันสับสน ระหว่าง Pydantic (ที่เป็น model ถูกใช้เพื่อจัดการข้อมูลที่เกี่ยวข้องกับ API) และ SQLAlchemy (ที่เป็น model ถูกใช้เพื่อการทำงานกับฐานข้อมูล) document ก็จะแนะนำว่า ให้แยกไฟล์เป็น

  • models.py สำหรับ SQLAlchemy model
  • schemas.py สำหรับ Pydantic models
  • การแยกสองส่วนนี้ออกจากกันช่วยให้โค้ดชัดเจนขึ้น และทำให้ง่ายต่อการเข้าใจว่าแต่ละโมเดลถูกใช้สำหรับงานอะไร

โดยขั้นตอนหลักๆที่เราจะต้องทำ (ต่อจาก step 2) คือ

  1. สร้าง Base Models ของ Pydantic ที่จะประกอบด้วย attribute ที่ใช้งานทั้ง สร้าง (creating) และ อ่าน (reading) โดย import จาก BaseModel ของ Pydantic
  2. สร้าง Pydantic Models สำหรับการตรวจสอบ “ข้อมูลขาเข้า” โดยจะถูกใช้เพื่อกำหนดโครงสร้างและประเภทของข้อมูลที่คาดว่าจะได้รับจากผู้ใช้ เช่น เมื่อผู้ใช้ต้องการสร้างหรือ update ข้อมูล (ส่วนใหญ่มักจะใช้ชื่อตามด้วย Create เช่น ItemCreate, UserCreate)
  3. สร้าง Pydantic Models สำหรับการ “ส่งข้อมูลขาออก” โดยจะถูกใช้เพื่อกำหนดโครงสร้างและประเภทของข้อมูลที่ส่งกลับไปยังผู้ใช้ เช่น เมื่อส่งผลลัพธ์จากการสืบค้นฐานข้อมูลกลับไปในรูปแบบ JSON (ส่วนตัว ผมจะชอบใช้ชื่อตามด้วย Response เช่น ItemResponse, UserResponse แต่ตาม document จะแนะนำให้ใช้ชื่อ Class เฉยๆ เช่น Item, User แทนได้เช่นกัน)
  4. สำหรับ “Model ขาออก” ให้เพิ่ม การตั้งค่า from_attributes = True ใน Config เพื่อช่วยให้ Pydantic สามารถแปลงข้อมูลจาก SQLAlchemy Models เป็น Pydantic Models ได้โดยอัตโนมัติ (ใน version เก่าและตาม document จะใช้ค่า orm_mode = True)

โดยอ้างอิงจากข้อมูลใน Code Step 2 เราจะสามารถเพิ่ม Pydantic หน้าตาประมาณนี้ออกมาได้

from pydantic import BaseModel
# Base Model
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
# Model ขาเข้า (ขาสร้าง)
class ItemCreate(Item):
pass
# Model ขาออก (ขาออก)
class ItemResponse(Item):
id: int
# ทำการเชื่อมเข้ากับ
class Config:
from_attributes = True

โดย การเพิ่ม from_attributes (orm_mode) เข้าไปใน Pydantic Models นั้น

  • จะช่วยให้ Pydantic สามารถแปลงข้อมูลจาก SQLAlchemy Models โดยอัตโนมัติ แม้ว่า SQLAlchemy Models จะไม่ได้ส่งข้อมูลมาในรูปแบบ dictionary ซึ่งเป็นรูปแบบที่ Pydantic มักจะคาดหวัง
  • โดยปกติแล้ว Pydantic คาดหวังข้อมูลในรูปแบบของ dictionary แต่ SQLAlchemy Models มักจะส่งข้อมูลในรูปแบบของ Python objects เมื่อมี orm_mode Pydantic จะสามารถรับและแปลงข้อมูลจาก SQLAlchemy Models เป็น Pydantic Models ได้โดยไม่มีปัญหา

เช่น หาก data เข้ามาเป็นแบบนี้

id = data["id"]

ก็จะสามารถ get จาก attribute โดยตรงเป็นแบบนี้ได้เลย

id = data.id

Step 4: ประยุกต์ใช้กับ CRUD Service

เมื่อเราประกาศทุกอย่างเรียบร้อย เราก็จะสามารถเรียกใช้งานการจัดการข้อมูลผ่าน Pydantic Model ได้เลย
code จะมีหน้าตาประมาณนี้

from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from typing import List, Optional
# เพิิ่ม sqlalchemy เข้าไป
from sqlalchemy import create_engine, Column, Integer, String, Float, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# ตั้งค่าและเชื่อมต่อกับฐานข้อมูล SQLite
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# สร้างโมเดลสำหรับฐานข้อมูล
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(Text, nullable=True)
price = Column(Float, nullable=False)
tax = Column(Float, nullable=True)
# สร้างฐานข้อมูล
Base.metadata.create_all(bind=engine)
# โมเดลข้อมูลสำหรับรับข้อมูลจาก API
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
class ItemCreate(Item):
pass
class ItemResponse(Item):
id: int
class Config:
from_attributes = True
app = FastAPI()
# Dependency สำหรับสร้าง Session กับฐานข้อมูลในแต่ละคำร้องขอ
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Create: สร้างสินค้าใหม่
@app.post("/items/", response_model=ItemResponse)
async def create_item(item: ItemCreate, db: Session = Depends(get_db)):
db_item = ItemDB(**item.model_dump())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
# Read: อ่านสินค้าทั้งหมด
@app.get("/items/", response_model=List[ItemResponse])
async def read_items(db: Session = Depends(get_db)):
return db.query(ItemDB).all()
# Read: อ่านสินค้าตาม ID
@app.get("/items/{item_id}", response_model=ItemResponse)
async def read_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(ItemDB).filter(ItemDB.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
# Update: อัปเดตสินค้าตาม ID
@app.put("/items/{item_id}", response_model=ItemResponse)
async def update_item(item_id: int, item: ItemCreate, db: Session = Depends(get_db)):
db_item = db.query(ItemDB).filter(ItemDB.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
for key, value in item.model_dump().items():
setattr(db_item, key, value)
db.commit()
db.refresh(db_item)
return db_item
# Delete: ลบสินค้าตาม ID
@app.delete("/items/{item_id}")
async def delete_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(ItemDB).filter(ItemDB.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(db_item)
db.commit()
return {"message": "Item deleted"}

ในการใช้งานจริง เราจะต้องสร้าง API endpoints ที่รับและส่งข้อมูลโดยใช้ Pydantic Models ซึ่งจะช่วยให้คุณจัดการข้อมูลที่มาจาก SQLAlchemy Models ได้ เพื่อให้ข้อมูลนั้นปลอดภัยต่อการ input เข้าไป + ช่วยเพิ่มเรื่องของ validation ก่อนที่จะเข้า database เข้าไปด้วย
เพิ่มเติมสำหรับ get_db() ที่เพิ่มมา เพื่อให้สามารถ access กับ database ได้

  • get_db() ทำหน้าที่สร้าง session ใหม่ (ผ่าน SessionLocal()) ในแต่ละคำร้องขอ และรับประกันว่าจะมีการปิด session นั้นเมื่อการทำงานเสร็จสิ้น ไม่ว่าจะสำเร็จหรือล้มเหลวก็ตาม ( finally: db.close()). สิ่งนี้สำคัญเพื่อป้องกันการรั่วไหลของทรัพยากร (resource leak) ซึ่งอาจเกิดขึ้นหาก session ไม่ถูกปิดหลังจากใช้งาน.
  • ใน FastAPI ฟังก์ชัน get_db() ถูกใช้เป็น dependency ซึ่งสามารถถูก inject เข้าไปในฟังก์ชันอื่น ๆ ผ่าน Depends(get_db) เพื่อให้ฟังก์ชันนั้นสามารถเข้าถึง session ของฐานข้อมูลได้โดยตรง สิ่งนี้ช่วยให้โค้ดมีความชัดเจนและง่ายต่อการทดสอบ เนื่องจาก FastAPI สามารถจัดการกับ dependency เหล่านี้ได้โดยอัตโนมัติ.

ไอเดียของการใช้งานก็จะประมาณนี้ สังเกตุว่า การจัดการทุกอย่างก็จะตรงไปตรงมาผ่าน Object ได้เลย โดยไม่จำเป็นต้องจัดการผ่าน SQL ได้เลย
และเพื่อให้เป็นระเบียบมากขึ้น เราสามารถจัดการ code structure ใหม่ให้เป็นแบบนี้ได้ (จากแต่เดิมทั้งหมดจัดการผ่าน main.py)

.
└── app
├── __init__.py
├── database.py
├── main.py
├── models.py
└── schemas.py

โดย ไฟล์ __init__.py* *เป็นเพียงไฟล์ว่าง ๆ แต่จะบอก Python ว่า app folder นี้เป็น package ที่สามารถเรียกหากันได้ และทำการแยกไฟล์ออกเป็นตามนี้

  • database.py สำหรับ จัดการเชื่อมต่อกับ database
app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
  • models.py เพื่อเก็บโครงสร้างของตารางในฐานข้อมูล
app/models.py
from sqlalchemy import Column, Integer, String, Float, Text
from app.database import Base
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(Text, nullable=True)
price = Column(Float, nullable=False)
tax = Column(Float, nullable=True)
  • schemas.py เพื่อเก็บโครงสร้างของข้อมูลที่ใช้รับส่งใน API
app/schemas.py
from pydantic import BaseModel
from typing import Optional
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
class ItemCreate(Item):
pass
class ItemResponse(Item):
id: int
class Config:
from_attributes = True
  • main.py สำหรับ code หลักของ application
app/main.py
from fastapi import FastAPI, HTTPException, Depends
from typing import List
from sqlalchemy.orm import Session
from app.database import Base, engine, get_db
from app.models import ItemDB
from app.schemas import ItemCreate, ItemResponse
# สร้างฐานข้อมูล
Base.metadata.create_all(bind=engine)
app = FastAPI()
# Create: สร้างสินค้าใหม่
@app.post("/items/", response_model=ItemResponse)
async def create_item(item: ItemCreate, db: Session = Depends(get_db)):
db_item = ItemDB(**item.model_dump())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
# Read: อ่านสินค้าทั้งหมด
@app.get("/items/", response_model=List[ItemResponse])
async def read_items(db: Session = Depends(get_db)):
return db.query(ItemDB).all()
# Read: อ่านสินค้าตาม ID
@app.get("/items/{item_id}", response_model=ItemResponse)
async def read_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(ItemDB).filter(ItemDB.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item
# Update: อัปเดตสินค้าตาม ID
@app.put("/items/{item_id}", response_model=ItemResponse)
async def update_item(item_id: int, item: ItemCreate, db: Session = Depends(get_db)):
db_item = db.query(ItemDB).filter(ItemDB.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
for key, value in item.model_dump().items():
setattr(db_item, key, value)
db.commit()
db.refresh(db_item)
return db_item
# Delete: ลบสินค้าตาม ID
@app.delete("/items/{item_id}")
async def delete_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(ItemDB).filter(ItemDB.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(db_item)
db.commit()
return {"message": "Item deleted"}

ก็จะช่วยทำให้ code แยกส่วนเป็นระเบียบมากขึ้นได้

การจัดการ install package วิธีอื่นๆ

https://fastapi.tiangolo.com/virtual-environments/?h=requirements#install-from-requirementstxt
requirements.txt ใน FastAPI (หรือในโปรเจค Python ทั่วไป) เป็นไฟล์ที่ใช้ระบุรายการของแพ็คเกจที่จำเป็นต้องติดตั้งเพื่อให้โปรเจคทำงานได้อย่างถูกต้อง โดยแต่ละบรรทัดในไฟล์จะระบุชื่อของแพ็คเกจและเวอร์ชันที่ต้องการ
ตัวอย่าง requirements.txt

fastapi[standard]==0.113.0
pydantic==2.8.0

โดยไฟล์ requirements.txt มีประโยชน์ดังนี้:

  1. การจัดการแพ็คเกจที่จำเป็น: ทำให้คุณสามารถติดตั้ง package ทั้งหมดที่จำเป็นสำหรับโปรเจคได้ง่าย ๆ ด้วยคำสั่ง pip install -r requirements.txt ซึ่งจะช่วยให้มั่นใจว่าโปรเจคของคุณใช้เวอร์ชันของ package ที่ถูกต้อง
  2. ความสามารถในการ share project: เมื่อเรา share** **project กับคนอื่น การมี requirements.txt จะช่วยให้ผู้ใช้งานคนอื่นสามารถตั้งค่าและรันโปรเจคได้โดยง่าย โดยไม่ต้องคาดเดาเกี่ยวกับเวอร์ชันของแพ็คเกจที่ควรจะติดตั้ง (มันคือ การจัดการ dependency เหมือนกับ package.json ใน npm)

ดังนั้น requirements.txt เป็นเครื่องมือสำคัญในการจัดการ dependencies และการทำให้โปรเจค Python ของเราสามารถ maintain ระยะยาวได้ดียิ่งขึ้นด้วยเช่นกัน

อ่าน .ENV

https://fastapi.tiangolo.com/advanced/settings/#reading-a-env-file
ถ้าเรามีการตั้งค่ามากมายที่อาจเปลี่ยนแปลงได้บ่อยๆ (เช่น config database url ที่แต่ละเครื่อง หรือแต่ละ environment อาจจะแตกต่างกัน) การเก็บค่าพวกนี้ไว้ในไฟล์แล้วอ่านค่าจากไฟล์แยกออกไปเหมือนกับเป็น environment variables อาจจะเป็นประโยชน์กับเคสเหล่านี้ได้ (เป็นเหมือนกับไฟล์ที่รับค่าเข้ามา แทนที่จะ fix ค่าเหล่านี้ไว้ในส่วนของ source code)

โดยวิธีปฏิบัตินี้เป็นที่รู้จักกันดี โดย environment variables เหล่านี้มักจะถูกเก็บไว้ในไฟล์ที่ชื่อว่า .env และไฟล์นี้จะถูกเรียกว่า “dotenv”

โดยตัว Pydantic นั้น ได้ support การอ่านค่า .env นี้ไว้ใน external library ของ pydantic คือ python-dotenv

https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support

เราสามารถทำการลง package python-dotenv ด้วยคำสั่ง

Terminal window
pip install python-dotenv

สมมุติ เราจะลองเปลี่ยนให้การ fix url ของ sqlite url จากแต่เดิมอยู่ใน code ให้มาอ่านผ่าน .env แทน เราก็สามารถเพิ่ม .env เป็นค่าตามนี้ได้

DATABASE_URL=sqlite:///./test.db

และในตัว code เราก็จะสามารถอ่านค่า DATABASE_URL ได้โดยใช้ library python-dotenv ด้วยวิธีตามนี้

import os
from sqlalchemy import create_engine, Column, Integer, String, Float, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class ItemDB(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(Text, nullable=True)
price = Column(Float, nullable=False)
tax = Column(Float, nullable=True)
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

เพียงเท่านี้ เราก็จะไม่มีการ fix code ค่า config ที่อาจจะแตกต่างกันในแต่ละเครื่องเอาไว้ใน code ได้แล้ว

Deploy ที่ไหนได้บ้าง

การ deploy FastAPI สามารถทำได้หลายวิธี รวมถึงแบบ manual และการใช้ Docker ทั้งสองวิธีนี้มีความแตกต่างกันทั้งในด้านการตั้งค่าและการจัดการ

1. การ Deploy แบบ Manual

https://fastapi.tiangolo.com/deployment/manually/
การ deploy FastAPI แบบ manual หมายถึงการตั้งค่าและรัน application บน server โดยตรง
โดย FastAPI นั้นได้เตรียมคำสั่งสำหรับการ run แบบ production ไว้นั่นคือ คำสั่ง

Terminal window
fastapi run

ผลลัพธ์ก็จะเป็นประมาณนี้
fast-python-07.webp
หลังจากนั้น โดยทั่วไปจะมีการใช้ reverse proxy เช่น Nginx เพื่อรับคำขอ (requests) จากผู้ใช้งานและส่งต่อไปยัง Uvicorn (ตัวผลลัพธ์จาก fastapi run ที่กำลัง run อยู่) เช่น config nginx ประมาณนี้

server {
listen 80;
server_name your_domain_or_ip;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
  • อาจจะใช้เครื่องมืออย่าง Supervisor หรือ Systemd เพื่อจัดการและทำให้ application ทำงานอยู่ตลอดเวลาได้

  • แนะนำให้ศึกษาเรื่องการ deploy web service เพิ่มเติมก่อนทำการ deploy ด้วยวิธีนี้

2. การ Deploy ด้วย Docker

https://fastapi.tiangolo.com/deployment/docker/
Docker เป็นเครื่องมือในการ containerization ที่ช่วยให้คุณสามารถแพ็คแอปพลิเคชันและ dependencies ทั้งหมดลงใน container ซึ่งทำให้การ deploy และการจัดการง่ายขึ้น
หลักการในการ deploy ด้วย Docker คือ

  1. สร้าง Dockerfile ที่เป็น Container run application python ตัว FastAPI ขึ้นมา
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
  • อย่างในเคสนี้ เราจะใช้การจัดการ package manager อย่าง requirements.txt เพื่อให้การจัดการ dependency นั้นง่ายขึ้น
  1. สร้าง Docker Image โดยใช้คำสั่ง docker build -t my-fastapi-app . เพื่อสร้าง Docker image โดยใช้ Dockerfile ที่เตรียมไว้
  2. run Docker Container ใช้คำสั่ง docker run -d --name fastapi-container -p 8000:8000 my-fastapi-app เพื่อ run container ของ application
  3. เมื่อสามารถ run image มาได้ ที่เหลือก็นำ image ตัวนี้ไป deploy กับ server ที่รองรับการ deploy แบบ Container ได้เช่น Google Cloud Run, Kubenetes, Docker compose บน machine เป็นต้น
    ** สำหรับการ deploy หากใครสนใจเพิ่มเติม สามารถดูในหัวข้อ “Deployment Go” ของช่อง Mikelopster เพื่อปูพื้นฐานของการ deploy งาน 1 ชิ้นก่อนได้
    https://docs.mikelopster.dev/c/goapi-essential/chapter-9/intro

สรุป

ตามที่เราเล่ามาทั้งหมด เราได้ share concept พื้นฐานของ FastAPI ไปว่า

  • เราสามารถสร้าง API ด้วย python ด้วย library FastAPI ได้อย่างไร
  • เราสามารถสร้าง CRUD API พื้นฐานของการทำ Backend API อย่างไรได้บ้าง
  • FastAPI จัดการ relational database อย่างไรผ่าน SQL Alchemy และ Pydantic
  • การจัดการ dependency ผ่าน requirement.txt และ config ผ่าน .env
  • เล่าไอเดียการนำ FastAPI ไป deploy production ว่าสามารถทำอย่างไรได้บ้าง

หวังว่า หัวข้อนี้จะเป็นไอเดียให้สำหรับคนที่ใช้ภาษา python เป็นหลักได้ทำความเข้าใจส่วนของ Backend Service มากขึ้นและได้นำ FastAPI ไปลองประยุกต์ใช้กันดูนะครับ

Related Post

Share on social media