ทดลองสร้าง API Endpoint

ทำความเข้าใจโครงสร้างของ FastAPI ⚡

หลังจากที่เราติดตั้ง FastAPI และทดลองรัน Code ตัวอย่างได้เรียบร้อยแล้ว คราวนี้เรามาทำความเข้าใจกันหน่อยว่า Code ที่เรารันไปเมื่อกี้นี้ มันทำงานยังไงกันนะ

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

Initializer

เริ่มจาก

app = FastAPI()

บรรทัดนี้เป็นการสร้าง Instance ของ FastAPI ที่เราได้ Import มานั่นเอง พูดให้เข้าใจง่าย ๆ คือ FastAPI คือ Class หรือ Blueprint ซึ่ง Programmer คนอื่น เขียนมาให้เราแล้ว เราสามารถนำ Blueprint มาสร้างเป็น Instance ของเราเพื่อใช้งานได้เลย โดยที่ไม่จำเป็นต้องเข้าใจว่าเบื้องหลัง FastAPI จะทำงานอย่างไร (แต่ใครที่อยากอ่านว่าข้างใน FastAPI จริง ๆ แล้วทำงานอย่างไร สามารถกด Ctrl + Left Click ที่ชื่อ FastAPI() ดูได้นะ)

คิดว่าถ้าได้เรียนวิชา Object Oriented Programming น่าจะเข้าใจสิ่งที่เรากำลังทำมากขึ้นนะ

Endpoint Declaration

API Endpoint คือ จุดสิ้นสุดของ API (?) ... เอาเป็นว่า มันคือ Method + Path ที่เฉพาะเจาะจง เพื่อ "ทำอะไรบางอย่าง" กับ Resource ที่เรามี เช่น

  • Endpoint GET /products หมายถึง หากมีการเรียกใช้งาน (Method + Path ที่ถูกต้อง) แล้ว จะมีการคืนค่าสินค้าทั้งหมดที่มีในระบบ

  • Endpoint GET /products/{barcode_no} หมายถึง หากมีการเรียกใช้งานและใส่ Path Variable เป็นหมายเลขบาร์โค้ดของสินค้า จะคืนข้อมูลรายละเอียดของสินค้านั้น ๆ

  • Endpoint POST /products หมายถึง หากมีการเรียกใช้งานและส่ง Request Body เป็นรายละเอียดของสินค้า ตาม Model ที่ Programmer ระบุ จะเพิ่มสินค้าใหม่ลงในระบบ

อย่างไรก็ตาม เมื่อเราประกาศ Endpoint หนึ่ง ๆ ขึ้นมา เช่น Endpoint ที่ใช้ในการเรียกดูสินค้าทั้งหมดในระบบ (GET /products) เราซึ่งเป็น Programmer ก็ต้องมาระบุเองอยู่ดีว่า การเรียกดูข้อมูลสินค้าในระบบทั้งหมดต้องทำอย่างไร

จากตัวอย่างที่เราทำไปเมื่อสักครู่ @app.get("/") คือ Decorator ที่บอกว่าเรากำลังจะสร้าง Endpoint สำหรับ Method GET ที่ path / หรือ Root path จากนั้น ฟังก์ชัน read_root() จะทำหน้าที่ในการประมวลผลและคืนค่า (Handle) Request นั้น ๆ

จากในตัวอย่าง ฟังก์ชัน read_root() จะทำหน้าที่เพียงคืนค่า {"Hello": "World"} กลับไป แต่พอเข้าใจไอเดียแล้วใช่มั้ยครับ ว่าเราจะทำอะไรใน ฟังก์ชัน read_root() ก็ได้ เพื่อให้มันส่งค่าอะไรกลับคืนไปก็ได้

แบบฝึกหัด !!

ไหนลองทำให้เวลาเราทำ GET Request ที่ path / แล้วมันคืนค่าเป็นจำนวนครั้งที่มีการเรียก Request มาที่ Path นี้หน่อยซิ เช่น

  • ครั้งที่ 1 -> {"Hello World": 1}

  • ครั้งที่ 2 -> {"Hello World": 2}

  • ครั้งที่ 3 -> {"Hello World": 3}

Hint:

  1. เราต้องแก้ไขฟังก์ชัน read_root() ให้เปลี่ยนค่าที่ Return กลับมา

  2. เราจะรู้ได้ยังไงนะว่ามีการ Request มาที่ path / กี่ครั้งแล้ว

เฉลย

ง่าย ๆ เลย คือเราก็แค่เพิ่มตัวแปรมาใช้นับว่ามีการ Request มาที่ path / กี่ครั้งแล้ว และทุกครั้งที่มีการ Request เข้ามา เราก็จะเพิ่มค่าตัวแปรนั้นไปเรื่อย ๆ

ในกรณีนี้จะใช้ตัวแปร visit ในการนับว่ามีการ Request มาแล้วกี่ครั้ง จากนั้น Return ค่าของตัวแปร visit ตามรูปแบบที่โจทย์กำหนด

ปล. เราใส่ global visit เพื่อบอกว่า เราจะใช้ตัวแปร visit จาก Global Scope มิฉะนั้น ฟังก์ชัน read_root() จะหาตัวแปร visit ไม่เจอ เพราะไม่อยู่ใน Scope ของตัวเอง โดยเราจะใช้ global เมื่อมีการเปลี่ยนแปลงค่าตัวแปรในฟังก์ชันเท่านั้น (สามารถอ่านค่าได้ตามปกติ)

Path Variable ✈️

Concept

จากที่เราได้ยกตัวอย่างไปข้างต้น บางครั้งเวลาเรา Request เราก็อยากใส่ตัวแปรบางอย่างเข้าไปใน Request ด้วย เช่น GET /products/{barcode_no} ซึ่งเป็นการแสดงข้อมูลสินค้าจากหมายเลขบาร์โค้ด แน่นอนว่าเราต้องใส่ตัวแปรเป็นหมายเลขบาร์โค้ดของสินค้า เพื่อให้ระบบสามารถหาข้อมูลสินค้านั้น ๆ และ Return ข้อมูลได้อย่างถูกต้อง

วันนี้ เราจะสร้าง Endpoint GET /greet/{name} โดยจะมี Response เป็น {"Hello": <name>} โดย <name> เป็น Variable ที่ผู้ใช้ป้อนเข้ามาผ่าน Path Variable เริ่มโดยการนำ Code ต่อไปนี้ วางต่อจาก Endpoint เมื่อกี้ได้เลย จะเป็นการเพิ่ม Endpoint มาอีกหนึ่ง Endpoint

สังเกตจาก Decorator ว่าเรามีการใส่ {name} ต่อท้าย /greet/ เพื่อระบุว่า เราจะรับค่าทั้งหมดที่อยู่หลังจาก /greet/ เป็น Path Variable ชื่อ name เช่น

  • GET /greet/Oak -> name = 'Oak'

  • GET /greet/1234 -> name = '1234'

  • GET /greet/YoYo -> name = 'YoYo'

จากนั้น ในขั้นตอนของการประกาศฟังก์ชัน เราสามารถรับ name เป็น Parameter ของฟังก์ชันได้ จากนั้น เราจะเอาตัวแปรนั้นมาใช้ทำอะไรในฟังก์ชันของเรา มันก็เป็นเรื่องของเราแล้ว !

Application

สำหรับการใช้งานจริง Endpoint GET /products/{barcode_no} จะมีการนำ barcode_no ซึ่งเป็น Path Variable ไป Query หาข้อมูลจาก Database (หรืออะไรก็ตาม) เช่นตามตัวอย่าง จะเก็บข้อมูล Product ไว้ใน List และเมื่อมีการ Request GET /products/{barcode_no} จะทำ barcode_no ไปค้นหาสินค้าที่มี barcode_no ตรงกัน มาเป็น Response

Logic ที่สำคัญ อยู่บริเวณบรรทัดที่ 16 - 22

สามารถนำ code ด้านบนไปรวมกับ Code ที่เราเขียนตอนแรกได้เลย (เท่ากับว่าตอนนี้เราจะมี 3 Endpoint) อย่างไรก็ตาม เราต้อง Import HTTPException จาก fastapi ก่อน

โดย Lab ที่จะให้ทดลองทำในหัวข้อนี้ เราจะเก็บข้อมูลต่าง ๆ โดยใช้ List เพื่อความง่ายในการพัฒนา (ไม่งั้นเราต้องอธิบายเรื่อง Database กันอีก) อย่างไรก็ตาม อย่าลืมว่าตัวแปรทั้งหมดจะหายไปหลังจากเรา Restart Web Server ของเรา ดังนั้นก็ระวังกันด้วยนะ

ตอนนี้ Project เราหน้าตายังไง ?

Request Body 📃

ต่อมา เราจะมาพูดถึง Request Body กัน ยกตัวอย่าง เราต้องการสร้าง Endpoint POST /products เพื่อเพิ่มสินค้าใหม่เข้าไปในระบบ โดยต้องการโครงสร้าง Request Body ดังนี้

เราสามารถกำหนดโครงสร้างของ Request Body ได้ โดยใช้ Pydantic BaseModelเริ่มต้นจากการ Import กันก่อน

จากนั้น สร้าง Model ข้อมูลขึ้นมา ในที่นี้จะใช้ชื่อ ProductDTO (Data Transfer Object) โดยสร้างเป็น Class ที่ Inherit มาจาก BaseModel (อ่านเพิ่มเติมได้ที่นี่)

ต่อมา สร้าง Endpoint ใหม่ของพวกเรา โดยใช้ @app.post(<path>) เพื่อ Handle POST Request และให้ ProductDTO เป็น Parameter ของฟังก์ชัน แล้วจึงเขียน Logic ในการเพิ่มสินค้าใหม่เข้าไปใน List สุดท้ายจึง Response เป็นข้อมูลของสินค้าที่เพิ่มเข้าไป

เสร็จแล้ววว ! เรามาลองส่ง Request กันดีกว่า อย่าลืมใส่ Request Body ตามที่เรากำหนดด้วยนะ

ทดลองส่ง POST Request ในโปรแกรม Postman

สังเกตว่าได้ Status Code เป็น 200 OK และ Response Body เป็นรายละเอียดของสินค้าที่เราเพิ่งเพิ่มไปตามที่เราเขียน Code เลย คราวนี้เรามาลองใช้ Endpoint GET /products/{barcode_no} เพื่อดูข้อมูลสินค้าที่เราเพิ่งเพิ่มกันไปดีกว่า โดยใช้หมายเลขบาร์โค้ดของสินค้าที่เราเพิ่งเพิ่มไปเป็น Path Parameter

ทดลองส่ง GET Request เพื่อดูข้อมูลสินค้าในโปรแกรม Postman

Data Validation

SwaggerUI 📚

ที่ผ่านมา เราใช้โปรแกรม Postman ในการทดสอบ API ของเรา อย่างไรก็ตาม ใน FastAPI ได้มี Built-in SwaggerUI ซึ่งเป็น Graphic Interface สำหรับ Document และ ทดสอบ API ได้ ซึ่งจะสร้างความสะดวกในการพัฒนาให้กับทั้ง Frontend และ Backend Developer

สามารถเข้าสู่หน้า SwaggerUI ได้ผ่าน GET /docs เพื่อความสะดวก สามารถเปิด http://localhost:8000/docs ใน Browser ได้เลย

SwaggerUI จะบอกตั้งแต่ API ของเรามี Endpoint ใดบ้าง แต่ละ Endpoint มีรูปแบบ Request Body อย่างไร และแต่ละ Endpoint มีรูปแบบ Response อย่างไร ทำให้ Frontend Dev ไม่ต้องวิ่งไปถาม Backend Dev บ่อย ๆ นั่นเอง

Security Issue

Conclusion 🔎

สรุปแล้ว เราได้เรียนรู้วิธีการสร้าง API Endpoint แต่ละ Method เพื่อจัดการกับ Resource ไม่ว่าจะเป็น Endpoint ที่มีและไม่มี Path Variable รวมถึง Endpoint ที่ต้องมีการรับ Request Body นอกจากนั้นยังเรียนรู้การทำ Data Validation สามารถสรุป Code ที่เราเขียนถึงปัจจุบันได้ดังนี้

ต่อไป เราจะนำความรู้เหล่านี้มารวม ๆ กัน แล้วสร้างเป็น Application ของเรากัน ซึ่งแน่นอนว่ามันจะมีมากกว่า 1 Object และ Object เหล่านั้นจะมีความสัมพันธ์บางอย่างต่อกัน (สำหรับรายละเอียดเพิ่มเติมจะได้เรียนในวิชา OOP)

Last updated

Was this helpful?