สร้าง API ความเร็วสูงด้วย FastAPI (Python) + Docker Compose

   By: Chaiwat Chaobaankoh

   อัปเดตล่าสุด April 27, 2024

สร้าง API ความเร็วสูงด้วย FastAPI (Python) + Docker Compose

บทความนี้ ผมจะมาแนะนำขั้นตอนในการสร้าง RESTful API โดยใช้ FastAPI และทำการ
deploy ด้วย Docker Compose เพื่อให้สามารถง่ายต่อการเรียกใช้งาน API และสามารถนำไปรันได้ทุกที่กันเลยครับ

Note: สำหรับเพื่อน ๆ ที่ยังไม่แม่น API ก็สามารถอ่านบทความนี้ได้เพิ่มเติมเลยครับ API คืออะไร ?


FastAPI คืออะไร

FastAPI เป็น Web Framework ของภาษา Python ที่ใช้สำหรับสร้าง API โดยเน้นที่ความเร็ว ประสิทธิภาพ และง่ายต่อการเขียน มันถูกสร้างขึ้นบนพื้นฐานของ ASGI (Asynchronous Server Gateway Interface) ซึ่งทำให้สามารถรองรับ Asynchronous Programming ได้ อีกทั้งยังมาพร้อมกับเครื่องมือที่ช่วยให้การสร้าง API เป็นเรื่องง่าย เช่น Automatic Documentation, Type Hints, Data Validation เป็นต้น

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


Pydantic

Pydantic เป็น Library ใน Python สำหรับการจัดการและ Validate ข้อมูลที่มีโครงสร้าง (Structured Data) โดยอาศัย Type Hints ซึ่งช่วยให้โค้ดอ่านง่ายและลดข้อผิดพลาด มีคุณสมบัติหลักคือ Data Validation, Data Serialization และ Data Documentation ที่ช่วยให้จัดการข้อมูล input และ output ได้อย่างมีประสิทธิภาพ โดย FastAPI เองก็ใช้ Pydantic ในการจัดการ Request และ Response Model นั่นเองครับ


ทำไมต้อง FastAPI ?

  • สำหรับเรื่องประสิทธิภาพนั้น FastAPI คือ Web Framework ที่เร็วที่สุดจากฝั่ง Python เลยครับ
  • Developer สามารถสร้าง API ที่มีประสิทธิภาพสูงได้ และสามารถสเกลเว็บหรือแอพได้เป็นอย่างดี
  • มีการทำ Documentation ให้อัตโนมัติ ด้วย Swagger UI และ ReDoc ทำให้ง่ายต่อการ ทดสอบและเรียกใช้งาน API Endpoints
  • มีความทันสมัย FastAPI เพิ่ม ฟีเจอร์ Type Hints และ Asynchronous ทำให้ Code ที่เขียน clean ขึ้น และมีการตรวจสอบโค้ดให้อัตโนมัติ
  • มี Built-in Security ซึ่ง FastAPI มี OAuth2 และ JWT Tokens มาให้ในตั
  • เป็น back-end เว็บเฟรมเวิร์คยอดนิยมของภาษา Python ในการพัฒนา API ในปัจจุบัน



FastAPI Performance (Image Source: ChistopherGS)


ทำไมต้อง Docker?

  • เนื่องจาก Docker สามารถแยก environment เป็นของตัวเองได้ทำให้ลดปัญหา "Work on my machine" แต่ "Not work on other machines !!"
  • Microservice-friendly ซึ่ง Docker นั้นรองรับการทำงานแบบ microservices เป็นอย่างดี
  • Docker Image สามารถใช้ได้กับหลายๆ environments ทำให้ง่ายต่อการ deploy โปรเจคท์
  • Lightweight (เบา) เนื่องจาก container ใช้ทรัพยากรน้อยกว่า
แนะนำ Docker คืออะไร


ข้อดีในการใช้ Docker ร่วมกับ FastAPI?

  • ทำให้การพัฒนา FastAPI ง่ายขึ้น
  • Developer ไม่ต้องปวดหัวเรื่อง Python เวอร์ชัน
  • Developer ไม่ต้องหัวร้อนเรื่อง Dependencies ไม่ตรงกัน
  • ทำให้ ทุกๆ Environment มีคุณลักษณะของ API ที่เหมือนกัน
  • Docker Container ช่วยให้การ Test, Lints code หรือ Deploy to Production ง่ายขึ้น


เริ่มต้นโปรเจคท์

สำหรับบทความนี้เราจะพาสร้าง Todo App กันครับเริ่มต้นด้วยสเต็ปด้านล่างนี้ได้เลย

เปิด Terminal ขึ้นมาแล้วสร้างโฟลเดอร์ที่ชื่อว่า todo-app  จากนั้นเข้าไปที่โฟลเดอร์
สร้างไฟล์ที่ชื่อว่า main.py
เตรียมสร้าง Virtual Environment สำหรับโปรเจคท์ todo-api ด้วยการ พิมพ์คำสั่ง

$ mkdir todo-app
$ cd todo-app
$ python -m venv env
$ source env/bin/activate
(env) $ pip install fastapi uvicorn

บทความแนะนำเพิ่มเติม: Python Virtual Environment ที่ Python Dev ทุกคนต้องรู้

โครงสร้างของโปรเจค

└── todo-api
├── main.py
└── env


main.py

สำหรับบทความนี้เราจะ setup ทุกอย่างไว้ที่ไฟล์นี้ ไม่ได้ทำการแยก module แต่อย่างใด

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI()

class Item(BaseModel):
name: str
completed: bool

# In-memory storage of to-do items
to_do_list: List[Item] = []

@app.get("/todos/", response_model=List[Item])
async def read_todos():
return to_do_list

@app.post("/todos/", response_model=Item)
async def create_todo(item: Item):
to_do_list.append(item)
return item

@app.put("/todos/{item_id}", response_model=Item)
async def update_todo(item_id: int, item: Item):
if item_id >= len(to_do_list):
raise HTTPException(status_code=404, detail="Item not found")
to_do_list[item_id] = item
return item

@app.delete("/todos/{item_id}")
async def delete_todo(item_id: int):
if item_id >= len(to_do_list):
raise HTTPException(status_code=404, detail="Item not found")
to_do_list.pop(item_id)
return {"detail": "Item deleted"}


ทดสอบ API

ให้ทำการเปิด Terminal หรือ Power Shell ในโปรแกรม Editor จากนั้นพิมพ์คำสั่งเพื่อรัน Server

$ uvicorn main:app --reload


ถ้าได้ output แบบนี้แสดงว่า API Server รันได้ปกติ



จากนั้นลองมาเช็คผ่าน Browser ได้เลย -->  http://127.0.0.1:8000


เราจะได้ output ตามภาพ


ซึ่งในส่วนของ FastAPI จะมี Swagger UI มาให้เราพร้อมใช้เลยครับ



Dockerize FastAPI App

Dockerfile

หลังจากที่เราทำในส่วนของ API เสร็จเรียบร้อยต่อมาเราก็มาสร้าง Dockerfile เพื่อทำการจับ FastAPI เข้าไปใน docker image กันครับ (อันนี้ต้องมีพื้นฐาน Docker กันนิดนึงเนอะ แต่ถ้ายังไม่มี เราก็มี คอร์ส Docker (Free)ให้เรียนฟรี ๆ จุใจในช่อง YouTube แบบจัดเต็มกันไปเลยครับ)


อันดับแรกเราต้อง Export Libraries ที่ใช้ในโปรเจคท์ของเราก่อน วิธีการก็ง่าย ๆ เลยครับ

$ pip freeze > requirements.txt


คำสั่งด้านบนจะเป็นการ Export Libraries ที่เราทำการติดตั้งไปตั้งแต่ตอนต้น แล้วจัดเก็บไปที่ไฟล์  requirements.txt  ประโยชน์ก็เพื่อให้เพื่อน คนในทีม หรือ เวลาเรา build docker มี Libraries ชุดเดียวกัน ตรงกัน หรือเหมือนกันกับเรา

เพื่อไม่ให้เกิดปัญหา Libraries conflict นั่นเอง หลังจากที่เรา พิมพ์คำสั่งเรียบร้อยก็จะได้ไฟล์เพิ่มขึ้นมา

└── todo-api
├── main.py
├── env
└── requirements.txt


จากนั้นเราก็สร้างไฟล์  Dockerfile ไว้ในโฟลเดอร์เดียวกันได้เลยครับ

#Dockerfile
FROM python:3.9-alpine3.18. #1

WORKDIR /code #2

COPY requirements.txt . #3

RUN pip install --no-cache-dir --upgrade -r requirements.txt #4

COPY . . #5

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] #6


เรามาดูในส่วนของ  Dockerfile  กันนิดนึงครับ

  • #1 นี่คือ Python base Image ที่เรา pull มาจาก Docker Hub เพื่อที่เราจะใช้เป็น base ของ Dockerfile อันนี้ครับ

  • #2 เป็นการกำหนด Working Directory หรือโฟลเดอร์ใน container ของเรา

  • #3 เราให้ทำการ copy ไฟล์  requirements.txt จากโปรเจคท์ในเครื่องของเรา เข้าไปที่ โฟลเดอร์ใน Docker Container

  • #4 ตรงนี้เป็นการสั่งให้ติดตั้ง libraries ทั้งหมดจากไฟล์   requirements.txt ของเรา

  • #5 จากนั้นใช้คำสั่ง  COPY เพื่อคัดลอกไฟล์หมด  . . จากโปรเจคท์ของเราเข้าไปไว้ที่ โฟลเดอร์ใน container

  • #6  CMD คือคำสั่งให้ Docker Image รัน command นี้ ตอนสตาร์ท Docker Container ครับ

Docker build

หลังจากที่สร้างไฟล์  Dockerfile  กันเรียบร้อยก็ถึงเวลาใช้งานกันครับ อันนี้เป็นโครงสร้างโปรเจคท์ปัจจุบันของเรานะครับ

└── todo-api
├── main.py
├── env
├── Dockerfile
└── requirements.txt


เปิด Terminal หรือ PowerShell และให้เข้ามาที่โปรเจคท์ของเราได้เลยครับ

จากนั้นทำการพิมพ์คำสั่ง

$ docker build -t todo-api:v1 .

คำสั่งตรงนี้ เป็นการสั่งให้ build Dockerfile ของเราที่โปรเจคท์นี้นะครับ ด้วยการบอก ที่อยู่ของ Dockerfile ของเราด้วย จุด . (dot) นั้นเองครับ

และให้สร้างด้วยชื่อ  tod-api  ซึ่งมี  -t  เป็น  v1  ครับ เพื่อให้แน่ใจว่าเราทำการ build docker image เสร็จเรียบร้อยให้พิมพ์คำสั่ง

$ docker image ls


เพื่อดูว่ามี docker image ชื่อ   todo-api  ที่เราเพิ่ง build ไป

$ docker images ls

REPOSITORY TAG IMAGE ID CREATED SIZE
todo-api v1 513251c9bdac 9 minutes ago 106 MB


ถ้าขึ้นชื่อ image ของเราก็ถือว่าการ build เสร็จสมบรูณ์ครับ


Docker run

docker run เป็นการนำ docker image ที่เราได้ทำการ build มาใช้งานซึ่งจะสร้างเป็น docker container และเราจะเรียกใช้ Aญ ของเราผ่าน docker container กันครับ


ทำการพิมพ์คำสั่ง

$ docker run -p 8000:80 --name my-first-api todo-api:v1


จากคำสั่งเราก็ทำการบอกให้ docker สร้าง container จาก docker image ที่ชื่อ todo-api ที่มี tag ชื่อว่า v1 และให้เข้าถึง container ด้วย port 8000 ซึ่ง port 8000 จาก host (เครื่องของเรา)

ก็จะเข้าถึง ตัว API ของเราที่อยู่ใน docker container ที่รันด้วย port 80 นั่นเองครับ และทำการตั้งชื่อ container ว่า my-first-api


ลองทดสอบเรียก API ของเราที่ Browser ได้เลยครับ ด้วย URL นี้ localhost:8000/docs




ลองทดสอบด้วยการเพิ่มข้อมูลด้วย http POST




ลองทดสอบด้วยการเรียกดูข้อมูลด้วย http GET




ทดสอบแล้ว API สามารถทำงานได้ปกติแสดงว่าการรันด้วย Container สำเร็จเรียบร้อยครับ

ทีนี้กลับมาที่ตัว Terminal ของเรากันครับ จะเห็นได้ว่า Terminal ของเราจะแสดงเป็น Log ของ API ถ้าเกิดเรากด Ctrl+c ก็จะเป็นการ Terminate หรือออกจากการรั้น container ครับ


ถ้าหากอยากให้ docker container รันแบบ background หรือรันอยู่เบื้องหลังเราต้องเพิ่มคำสั่งเข้าไปครับ ให้ทำการกด Ctrl+c เพื่อออกจาก container ก่อนครับ


จากนั้นทำการลบ container ที่ชื่อ  my-first-api  ก่อน (ไม่เช่นนั้นแล้วเราจะไม่สามารถใช่ซื่อเดิมได้ครับ)

$ docker rm -f my-first-api


หลังจากพิมพ์คำสั่งแล้วกด Enter เพื่อลบ container ที่ชื่อ my-first-api เรียบร้อยแล้วให้ทำการพิมพ์ด้วยคำสั่งนี้ต่อครับ

$ docker run -d -p 8000:80 --name my-first-api todo-api:v1


 -d  หรือ  --detach  หมายถึงให้ Docker container รันแบบ background นั่นเองครับ

ทีนี้เราจะไม่เห็นในส่วนของ output แบบก่อนหน้าแล้ว (ตอนที่ยังไม่ใส่ Flag -d)


เราจะได้เป็นหมายเลข ของ Docker container มาแทน


Docker logs

เนื่องจากเรารัน Docker container แบบ background ถ้าหากเราต้องการดู Logs ของ API เราจะทำได้ยัง?


ง่ายมากครับ ให้ใช้คำสั่ง

$ docker logs <CONTAINER_NAME>


ในกรณี my-first-api ของเราให้พิมพ์คำสั่งนี้ครับ

$ docker logs my-first-api -f

 -f หรือ follow เป็นการให้ Logs แสดงข้อมูลแบบ Real time นั่นเองครับ


Docker Compose

Docker Compose เป็นเครื่องมือที่เอาไว้จัดการ container แบบหลายๆ ตัว สำหรับ Docker Compose เราจะใช้ ไฟล์ YAML เป็นตัว config app ของเรานั่นเองครับ ทำให้ง่ายต่อการจัดการ Docker container คือเราไม่ต้องมานั่งพิมพ์คำสั่งให้ยาวเหมือนด้านบนครับ แต่เราประกาศไว้ใน ไฟล์ docker-compose.yaml แทน


Use case?

อยาก Develop ตัว API ด้วย Dockerโดยที่ไม่ต้องมาคอย build docker image ใหม่ตลอด

ตรงนี้เราต้องปรับในส่วนของ Dockerfile นิดหน่อยครับ

FROM python:3.9-alpine3.18

WORKDIR /code

COPY requirements.txt .

RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY . .


ให้ตัดในส่วนของ CMD ออกไปเพราะว่า ในส่วนของ Docker image ให้ทำแค่

  • ติดตั้ง libraries หรือ dependencies ต่าง ๆ ที่ต้องใช้ในโปรเจคท์

  • Copy ซอร์สโค้ดไปไว้ที่โฟลเดอร์ใน container


จากนั้นทำการสร้างไฟล์ docker-compose.yaml ในโปรเจคท์เดียวกันได้เลยครับ

└── todo-api
├── main.py
├── env
├── Dockerfile
├── docker-compose.yaml
└── requirements.txt


ทำการแปะ config file ชุดนี้ลงไปที่ไฟล์ docker-compose.yaml ได้เลยครับ

version: '3.8' #1

services: #2
my-first-api: #3
build: . #4
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80", "--reload"] #5
volumes: #6
- .:/code #7
ports: #8
- "80:80"


อธิบายในส่วนของ docker-compose.yaml กันนิดนึงครับ

  • #1  version เป็นการกำหนดเวอร์ชั่นของ Docker Compose สามารถดูได้จาก compose-versioning

  • #2   services ประกาศ services ไว้สำหรับ จัดการกับ container

  • #3 ชื่อ Service หรือ Container.

  • #4.  build  เราสั่งให้ Compose ทำการ build image จาก Dockerfile ของเรา

  • #5   command ให้ Compose run คำสั่ง เวลารัน Container

  • #6  volumes กำหนดที่จัดเก็บไฟล์ของ container

  • #7 สั่งให้ทำการ เชื่อมต่อ โฟล์เดอร์ปัจจุบันของเรา ไปที่ โฟลเดอร์ code ใน container

  • #8  portsคือการกำหนด port ให้เข้าถึง container ผ่าน port 80


จาก Compose config เราจะเห็นได้ว่า การจัดการ Docker container ดูง่ายขึ้นกว่าเดิมเยอะเลยครับ


Docker Compose up

ถึงขั้นตอนในการรัน Docker Compose กันแล้วครับ ในส่วนของการรันก็จะมีสองแบบได้แก่

  • รันแบบปกติ คือให้แสดง Logs ทาง terminal เลยถ้ากด Ctrl+c ก็จะออก

  • รันแบบ Background หรือ ก็คือแบบ detach นั่นเองครับ


สำหรับการรันแบบปกติ ให้ใช้คำสั่ง

$ docker-compose up


จะได้ผลลัพท์ตามรูป



สำหรับการรันแบบ Background ให้ใช้คำสั่ง

$ docker-compose up -d



จะได้ผลลัพธ์ตามรูป



ให้ใช้คำสั่งนี้ในกรณีที่เรารันแบบ Background ถ้าหากเราต้องการเช็คว่า Docker Compose สามารถรันได้หรือไม่

$ docker-compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
todo-api-my-first-api-1 todo-api-my-first-api "uvicorn main:app --host 0.0.0.0 --port 80 --reload" my-first-api 5 hours ago Up 5 hours 0.0.0.0:80->80/tcp


หรือใช้ docker ps ก็ได้เช่นกัน


ให้ทำการเช็ค API ของเราว่าสามารถทำงานได้ปกติโดยเข้าไปที่ localhost:80/docs



ลองทำการแก้ไข ชื่อ route ใน POST จาก  /todos/  ให้เป็น  /todo/

@app.post("/todo/", response_model=Item)
async def create_todo(item: Item):
to_do_list.append(item)
return item


จากนั้นทำการ Save ไฟล์และ refresh browser อีกครั้ง


จะเห็นได้ว่ามีการอัพเดทตามโค้ดที่เราได้แก้ไขไป ทำให้เราสามารถใช้ Docker Compose เพื่อรันสำหรับ Develop ได้แล้ว และที่สำคัญคือ การคอนฟิกแบบ Compose ทำให้สะดวกว่าแบบ command ซึ่งจากจุดนี้ทำให้เราสามารถแก้ไข Docker ได้ง่ายขึ้นครับ


เป็นยังไงกันบ้างครับกับ สำหรับการสร้าง FastAPI และ ใช้ Docker เข้ามาช่วยจากการลงมือทำข้างต้น หวังว่าเพื่อนจะได้ความรู้ในส่วนของการใช้ FastAPI สร้าง API ขึ้นมา และได้รู้ถึงขั้นตอนการ Containerization ด้วย Docker และต่อยอดไปถึง Docker Compose กันเลยทีเดียวครับ หวังว่าเพื่อนๆ สามารถนำความรู้จากบทความนี้ไปประยุกต์ใช้กันได้ครับ


(ช่วง Promo) 📒 คอร์สเรียน Full Stack Developer 2024 by devhub.in.th คอร์สเดียวเนื้อ ๆ เน้น ๆ ของเพจ


ศึกษาเพิ่มเติมเกี่ยวกับ FastAPI ได้ที่ FastAPI Official Website


เปิดโลกการเขียนโปรแกรมและ Software Development ด้วย online courses ที่จะพาคุณอัพสกิลและพัฒนาสู่การเป็นมืออาชีพ เรียนออนไลน์ เรียนจากที่ไหนก็ได้ พร้อมซัพพอร์ตหลังเรียน

เรียนเขียนโปรแกรม