FastAPI สู่ความสนุกและความเร็วในการสร้าง API
FastAPI เป็นเฟรมเวิร์ก Python ที่ทำให้การสร้าง API เป็นเรื่องง่ายและมีประสิทธิภาพ ด้วยการใช้งาน FastAPI, คุณสามารถพัฒนา API ที่มีประสิทธิภาพสูงและรวดเร็วได้อย่างง่ายดาย ในบทความนี้ และบทความนี้เราจะใช้ VSCODE ในการเขียนโค้ดกันนะครับ และเชื่อว่าหลายๆท่านก็คงใช้ มั้ง 😅
ติดตั้ง FastAPI และ Uvicorn
เริ่มต้นด้วยการติดตั้ง virtualenv กันก่อนแน่นอนว่าจะไปแบบเร็วๆเลย :
python -m venv _env
- CMD
- PowerShell
- BASH
"venv/Scripts/activate"
venv/Scripts/activate
source venv/bin/activate
มาเริ่มติดตั้ง FastAPI กัน:
pip install fastapi
แน่นอนว่าเราต้องใช้ตัวที่จะมาช่วยในการ run web server ก็คือ uvicorn:
pip insatll uvicorn
ติดตั้งเครื่องมือสำหรับยิง API กัน 🔨
- ตัวหลักก็คือ Postman ดาวโหลดและติดตั้งฟรีได้เลยที่ https://www.postman.com/
- ตัวต่อมาคือ https://hoppscotch.io/ เอามาเป็นตัวเลือกให้ลองใช้ไม่จำเป็นต้องโหลดสามารถใช้บนบราวเซอร์ได้เลย
- และหากไม่อยากออกจาก VSCODE สามารถดาวนโหลด extension Postman ได้ที่ https://marketplace.visualstudio.com/items?itemName=Postman.postman-for-vscode
สร้าง 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/