Skip to main content

One post tagged with "fastAPI"

View All Tags

· 7 min read

FastAPI สู่ความสนุกและความเร็วในการสร้าง API

FastAPI เป็นเฟรมเวิร์ก Python ที่ทำให้การสร้าง API เป็นเรื่องง่ายและมีประสิทธิภาพ ด้วยการใช้งาน FastAPI, คุณสามารถพัฒนา API ที่มีประสิทธิภาพสูงและรวดเร็วได้อย่างง่ายดาย ในบทความนี้ และบทความนี้เราจะใช้ VSCODE ในการเขียนโค้ดกันนะครับ และเชื่อว่าหลายๆท่านก็คงใช้ มั้ง 😅

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

เริ่มต้นด้วยการติดตั้ง virtualenv กันก่อนแน่นอนว่าจะไปแบบเร็วๆเลย :

python -m venv _env
"venv/Scripts/activate"

มาเริ่มติดตั้ง FastAPI กัน:

pip install fastapi

แน่นอนว่าเราต้องใช้ตัวที่จะมาช่วยในการ run web server ก็คือ uvicorn:

pip insatll uvicorn

ติดตั้งเครื่องมือสำหรับยิง API กัน 🔨

  • ตัวหลักก็คือ Postman ดาวโหลดและติดตั้งฟรีได้เลยที่ https://www.postman.com/

  • ตัวต่อมาคือ https://hoppscotch.io/ เอามาเป็นตัวเลือกให้ลองใช้ไม่จำเป็นต้องโหลดสามารถใช้บนบราวเซอร์ได้เลย

สร้าง API ง่าย ๆ ⛏️

สร้างไฟล์ main.py และเขียนโค้ดต่อไปนี้:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

รัน FastAPI ด้วย Uvicorn:

uvicorn main:app
  • main คือชื่อไฟล์ main.py

  • app คือชื่อตัวแปร app = FastAPI()

หากต้องการใช้ server รีโหลดอัตโนมัติทุกครั้งที่บันทึก สามารถทำแบบนี้ได้:

uvicorn main:app --reload

แล้วจะได้เลขไอพีออกมาในลักษณะนี้ http://127.0.0.1:8000

นำไปทดสอบยิง API ด้วย Postman กัน ⛏️

กดที่เครื่องหมาย + ได้เลย:

จะมี Method ให้เลือกมากมาย ตอนนี้เราเลือก Get ตัวอื่นๆเดี๋ยวเราจะเลือกในอนาคตกัน

วาง http://127.0.0.1:8000 ลงในช่อง Enter URL text

จากนั้นกด Enter หรือปุ่ม Send ได้เลย

เราจะได้ข้อมูลที่ถูก return ออกมาแล้วตามโค้ดที่เราเขียนไว้

ประมาณนี้คร่าวๆเกี่ยวกับการใช้ Postman เราจะเขียนโค้ดกันต่อ

Method ใน FastAPI

FastAPI รองรับวิธีการ HTTP หลักๆ ดังนี้:

  • GET: ใช้สำหรับดึงข้อมูลจากเซิร์ฟเวอร์

  • POST: ใช้สำหรับส่งข้อมูลไปยังเซิร์ฟเวอร์

  • PUT: ใช้สำหรับอัปเดตข้อมูลบนเซิร์ฟเวอร์

  • PATCH: ใช้สำหรับอัปเดตบางส่วนของข้อมูลบนเซิร์ฟเวอร์

  • DELETE: ใช้สำหรับลบข้อมูลบนเซิร์ฟเวอร์

นอกจากนี้ FastAPI ยังรองรับวิธีการอื่นๆ เพิ่มเติม เช่น:

  • OPTIONS: ใช้สำหรับดึงข้อมูลเกี่ยวกับตัวเลือกที่มีสำหรับ endpoint

  • HEAD: ใช้สำหรับดึง header ของ response โดยไม่ต้องดึง body

โดยการใช้ decorator กับ method ลักษณะนี้:

  • @app.get : ใช้สำหรับ GET method

  • @app.post : ใช้สำหรับ POST method

  • @app.put : ใช้สำหรับ PUT method

  • @app.delete : ใช้สำหรับ DELETE method

  • @app.patch : ใช้สำหรับ PATCH method

  • @app.options : ใช้สำหรับ OPTIONS method

  • @app.head : ใช้สำหรับ HEAD method

Method GET

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

จากโค้ดคุณจะเห็นว่า .get(“/”) ภายในวงเล็บคือชื่อ path ที่เราจะระบุใช้งานอย่างเช่นในการทดสอบที่ Postman เช่น http://127.0.0.1:8000/ แต่ถ้าเปลี่ยนเป็น .get(“/hello”) ในการใช้ URL จะเป็นลักษณะนี้ http://127.0.0.1:8000/hello ในโค้ดคุณอาจจะเป็น async อยู่ก่อนการประกาศฟังก์ชัน นั่นก็เพื่อให้มันทำงานแบบ asynchronous นั่นเอง ก็คิดง่ายๆว่าเรา เข้าเว็บนึงแล้วจะดูข้อมูลอะไรสักอย่างถ้าเน็ตเราไม่ได้เราก็ต้องทำการรอจนกว่าข้อมูลจะมาครบเราถึงจะเอาข้อมูลในเว็บนั้นไปใช้งานได้ ก็คล้ายๆกับ async โดยในส่วนที่รอจะเรียกว่า await รอจนกว่าจะเสร็จค่อยไปทำอย่างอื่นต่อประมาณนั้น

คุณสามารถส่งข้อมูลเข้ามาได้ในลักษณะนี้ :

@app.get("/{message}")
async def hello_world(message):
return {"message": message}

เมื่อเทสใน Postman url คือ http://127.0.0.1:8000/hello wk:

หรือคุณอยากรับข้อมูลมากกว่า 1 ตัว:

@app.get("/{message}/{name}")
async def hello_world(message, name):
return {"message": f"{message} - {name}"}

ทดสอบใน Postman url คือ http://127.0.0.1:8000/hello/wk:

หรือคุณอยากจะเอาข้อมูลของ name จาก url http://127.0.0.1:8000/?name=wk นี้ก็ทำได้แบบนี้:

from fastapi import FastAPI, Request
from models import User

app = FastAPI()


@app.get("/")
async def hello_world(request: Request):
name = request.query_params.get("name")
return {"message": name}

ลองทดสอบเหมือนเดิมเลย

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

Method POST

เราจะทำการสร้าง method post กันโดยใช้ชื่อว่า /add-user และ ตั้งชื่อฟังก์ชันเป็นอะไรก็ได้ แต่พยายามตั้งให้มันเหมือนสมกับ path หน่อยเพื่อจะได้ไม่งงทีหลังแล้วกันนะ โดยเราจะทำการรับข้อมูลผ่าน request โดย request จะเป็น payload ที่เราส่งเข้ามาซึ่งในส่วนของ payload คืออะไรก็คือก้อน object หรือ json นั่นเองที่เราจะต้องส่งเข้ามา โดยลักษณะข้อมูลจะเป็นลักษณะนี้

{
"name":"test"
}

แล้วเราก็จะเอามาเก็บใส่ตัวแปร name แล้ว return ข้อมูลออกไปแบบนี้:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.post("/add-user")
async def add_user(request: dict):
name = request.get('name')
return {"message": name}

เมื่อเราเอาไปเทสใน Postman ทำการเปลี่ยน URL เป็น http://127.0.0.1:8000/add-user และเปลี่ยน raw ด้วย ก็จะเป็นลักษณะนี้:

จากนั้น payload นี้ไปใส่ แล้วกด Send ได้เลย:

{
"name":"test"
}

การใช้ Pydantic

Pydantic เป็นไลบรารีที่ใช้สำหรับการประกาศโมเดลข้อมูล (data models) ใน FastAPI โดยมีวัตถุประสงค์เพื่อช่วยให้เราสามารถกำหนดรูปแบบของข้อมูลที่เราต้องการรับหรือส่งออกได้อย่างง่ายดาย นอกจากนี้ Pydantic ยังมีฟีเจอร์การตรวจสอบความถูกต้องของข้อมูลที่มีประสิทธิภาพ ทำให้เราสามารถตรวจสอบและจัดการข้อผิดพลาดที่เกิดขึ้นได้อย่างมีประสิทธิภาพ

เพื่อให้โค้ดเรานั้นดูดีและเมื่อนักพัฒนาคนอื่นมาดูโค้ดเราจะได้ง่ายเหมือนกันด้วยจำเป็นต้องใช้ Pydantic มากำหนดทิศทางหรือสิ่งที่ต้องการเข้ามาจาก payload ใน FastAPI เราสามารถประกาศโมเดลข้อมูลโดยใช้ Pydantic ได้โดยใช้เครื่องหมาย : เพื่อระบุชนิดข้อมูลของแต่ละฟิลด์ ตัวอย่างการประกาศโมเดลข้อมูลด้วย Pydantic แบบนี้:

from pydantic import BaseModel

class User(BaseModel):
id: int
username: str
email: str

เรามารวมกันโค้ดเราก็จะเป็น:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
id: int
username: str
email: str

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.post("/add-user")
async def add_user(user: User):
username = user.username
return {"message": username}

ทดสอบใน Postman ด้วย payload:

{
"id":1,
"username":"test",
"email":"test@test.com"
}

หากกรณีไม่ครบเช่น:

{
"id":1,
"username":"test"
}

แต่เพื่อความ clean ก็ควรแยกส่วนออกไปเป็นอีกหนึ่งไฟล์จะดีกว่าเพราะงั้นสร้างไฟล์ใหม่ชื่อว่า models.py เพื่อเก็บ class User(BaseModel): ไว้ต่างหากแล้วค่อย import เข้ามาใช้งานงั้นเริ่มกัน

ในไฟล์ models.py:

from pydantic import BaseModel

class User(BaseModel):
id: int
username: str
email: str

ในไฟล์ main.py:

from fastapi import FastAPI
from models import User

app = FastAPI()

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.post("/add-user")
async def add_user(user: User):
username = user.username
return {"message": username}

สวยงามมากกว่า และอนาคตหากจะสร้าง class pydantic ก็ไปสร้างที่ไฟล์ models.py แล้วค่อยเรียกออกมาใช้งานลักษณะนี้

Method Put

จะใช้ในการแก้ไขข้อมูลทั้งหมดหรือเป็นการเปลี่ยนค่าใหม่ทั้งหมดของข้อมูล ตัวอย่างเช่น

  • นาย ก ชอบกินลูกอม และชอบสีเหลือง

ใช้ put ในการเปลี่ยนข้อมูล

  • นาย ก ชอบขนม และชอบสีดำ

อะไรประมาณนั้น เพื่อให้เห็นภาพตอนที่เรา post เรายังไม่ได้เก็บข้อมูลอะไรไว้เลยเพราะงั้นจะไปแก้กันก่อนลุย

from fastapi import FastAPI
from models import User

app = FastAPI()

users_list = []

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.get("/users")
async def users():
return users_list

@app.post("/add-user")
async def add_user(user: User):
users_list.append(user)
return {"message": "เพิ่มผู้ใช้งานเรียบร้อยแล้ว"}

โดยสร้างตัวแปร users_list ขึ้นมาเป็น global และเพิ่มเส้น /users เพื่อแสดงข้อมูลผู้ใช้งานทั้งหมด ในเส้น /add-user หากมีการส่ง payload เข้ามาครบก็จะถูกเพิ่มเข้าไปใน users_list

ต่อไปเราจะไปทำการเขียนเส้น method put กัน:

from fastapi import FastAPI
from models import User

app = FastAPI()

users_list = []

@app.get("/")
async def hello_world():
return {"message": "สวัสดี FastAPI!"}

@app.get("/users")
async def users():
return users_list

@app.post("/add-user")
async def add_user(user: User):
users_list.append(user)
return {"message": "เพิ่มผู้ใช้งานเรียบร้อยแล้ว"}

@app.put("/edit-user/{user_id}")
async def edit_user(user: User, user_id: int):
if len(users_list) > 0 :
for i in range(len(users_list)):
if users_list[i].id == user_id:
users_list[i] = user
else:
return {"message": "ไม่พบผู้ใช้งาน"}
return {"message": "อัพเดทผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

ในการทำงานของโค้ดคือจะทำการรับ id ผู้ใช้งานที่จะแก้ไขเข้ามาแล้วทำไการเช็คว่า len(users_list) > 0 เท่าเพื่อป้องกันกรณีไม่ได้มีผู้ใช้งาน จากนั้นทำการวนซ้ำเพื่อเปลี่ยนแปลงข้อมูลตาม payload ที่รับเข้ามา

นี่คือตัวอย่างการใช้ PUT ก็จะทรงๆนี้ครับ

Method PATCH

บางท่าเราก็ไม่ได้อยากแก้ไขข้อมูลมันทุกตัวหรอก เราจะแก้บางตัวเท่านั้น เพราะงั้นการมีอยู่ PATCH เลยเข้ามาช่วยตรงนี้จะจำง่ายๆก็เหมือนกับอัปเดตแพทช์อะไรประมาณนี้ เพราะอัปเดตแพทช์คืออัปเดตอะไรบางอย่างหรือเปลี่ยนแปลงบางอย่างไม่ใช่ว่าจะเปลี่ยนทุกระบบในเกมงั้นมาดูตัวอย่างโค้ดกัน

@app.patch("/update-user/{user_id}")
async def update_user(user: User, user_id: int):
if len(users_list) > 0 :
for i in range(len(users_list)):
if users_list[i].id == user_id:
users_list[i] = User(**user.dict())
else:
return {"message": "ไม่พบผู้ใช้งาน"}
return {"message": "อัพเดทผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

จากโค้ดรูปแบบคล้ายกับ put มาก แต่ที่ต่างคือ users_list[i] = User(**user.dict())

ทดสอบใน Postman กัน payload คือ

    {
"id":1,
"username":"admin_1234"
}

ผลลัพธ์จะบอกว่าต้องส่ง email มาด้วยนะ แต่ถ้าเราส่ง email ไปมันก็จะเป็น Put ไม่ใช่หรือ เพราะงั้นเราจะไปแก้ไขกันที่ไฟล์ models.py เราจะใส่ค่าเริ่มต้นกรณีไม่ใช่ payload เข้ามา ในลักษณะแบบนี้

from pydantic import BaseModel


class User(BaseModel):
id: int
username: str
email: str = ""

กลับไปแก้ main.py เพื่อดักข้อมูลกันต่อ

@app.patch("/update-user/{user_id}")
async def update_user(user: User, user_id: int):
if len(users_list) > 0:
for i in range(len(users_list)):
if users_list[i].id == user_id:
if user.email == "":
user.email = users_list[i].email
users_list[i] = User(**user.dict())
else:
return {"message": "ไม่พบผู้ใช้งาน"}
return {"message": "อัพเดทผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

โดยจะทำการดักถ้าไม่ได้ส่ง email ก็จะไปดึงข้อมูลเก่าที่อยู่ใน users_list ออกมา

    if user.email == "":
user.email = users_list[i].email

ทำการทดสอบกัน

แล้วดูข้อมูลกัน

Method DELETE

ส่วนตรงนี้จะง่ายมากทำคล้ายๆกับที่ผ่านๆมาเราต้องการแค่ user_id แล้วจะทำไปลบออกใน users_list และเราจะสร้างเส้น /delete-user/{user_id} ในลักษณะนี้ เป็นยังไงมาลุยกันเลย

@app.delete("/delete-user/{user_id}")
async def delete_user(user_id: int):
if len(users_list) > 0:
for i in users_list:
if i.id == user_id:
users_list.remove(i)
return {"message": "ลบผู้ใช้งานเรียบร้อยแล้ว"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

ทำการทดสอบกัน โดยเราจะไม่ส่ง payload ไปเพราะเราจะส่งคล้ายกับ get โดยข้อมูลที่มีจะมีสองก้อน

จากนั้นเราจะไปลบกันด้วยเส้นที่เราสร้างขึ้นมากัน

กลับไปดูข้อมูลทั้งหมดกัน

ต่อไปคือส่วนเสริม

  • สร้างเส้น get-user/{user_id}

ข้อมูลที่เราดึงมาตอนนี้คือเป็นการดึงข้อมูลทั้งหมดออกมา แต่เราอยกาดึงข้อมูลแค่ตัวหล่ะทำยังไง เราก็จะสร้างเส้น get-user/{user_id} แล้วส่งข้อมูลแค่ตัวเดียวออกไปกันลุย

@app.get("/get-user/{user_id}")
async def get_user(user_id: int):
if len(users_list) > 0:
for i in users_list:
if i.id == user_id:
return i
return {"message": "ไม่พบผู้ใช้งาน"}
else:
return {"message": "ไม่พบผู้ใช้งาน"}

มาทดสอบกัน

  • สร้าง autoid

จะดีกว่าไหมอยากให้ระบบสร้างเลข id เรียงให้เราอัตโนมัติไปเลย ตอนส่ง payload จะได้ไม่ต้องกังวลเรื่องของ id ซ้ำกันไป เพราะงั้นมาลุยกัน

แน่นอนว่าเราจะไม่ id ไปใน payload แล้ว เพราะงั้นเราจะไปแก้ในไฟล์ models.py ให้ id มีค่าเริ่มต้นกัน

from pydantic import BaseModel


class User(BaseModel):
id: int = 0
username: str
email: str = ""

และไปแก้ในไฟล์ main.py ที่เส้น add-user โดยเราจะทำแบบง่ายที่สุดคือนับจำนวนของ users_list ว่ามีกี่ตัวโดยใช้ len() เข้ามาช่วยเท่ากับว่าถ้าไม่มีสมาชิกอะไรในนั้นมันจะได้ 0 ออกมานั่นเองมาดูโค้ดกัน

@app.post("/add-user")
async def add_user(user: User):
user.id = len(users_list)
users_list.append(user)
return {"message": "เพิ่มผู้ใช้งานเรียบร้อยแล้ว"}

มาทดสอบกันเลยโดย payload คือ

{
"username":"test",
"email":"rtes@cx.cd"
}

แล้วลอง Send สักสองสามคลิกดู แล้วไปทดสอบเส้น /users อีกครั้งก็จะเห็นว่ามันมีเรียงให้อัตโนมัติแล้ว!!

และนี่ก็คือคร่าวๆของ FastAPI โดยบทความต่อไปเราจะลองทำการเชื่อมต่อกับ database อย่าง mongoDB หรือรับ JWT เสร็จลง cookie กันดู ซึ่งก่อนหน้ามีบทความเกี่ยวกับเชื่อมการ Authentication ด้วย firebase สามารถไปลองดูกันเพิ่มเติมได้นะที่ https://medium.com/@watchakorn-18k/%E0%B8%81%E0%B8%B2%E0%B8%A3-authentication-%E0%B9%83%E0%B8%99-fastapi-%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2-firebase-admin-sdk-94c4eb1040d4 หวังว่าจะมีประโยชน์กันนะครับแล้วเจอกันใหม่

Github : https://github.com/watchakorn-18k

Linkedin : https://www.linkedin.com/in/watchakorn/