FastAPI đã trở thành lựa chọn mặc định cho nhiều đội khi xây dựng API bằng Python. Điểm mạnh của nó không nằm ở tốc độ như tên gọi gợi ý, mà ở chỗ nó dùng gợi ý kiểu của Python để làm những việc mà trước đây bạn phải viết tay.
Cài đặt và chạy thử
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
Tạo file main.py:
from fastapi import FastAPI
app = FastAPI(title="API Quản lý sách")
@app.get("/")
async def root():
return {"thong_bao": "API đang hoạt động"}
Chạy máy chủ phát triển:
fastapi dev main.py
Mở http://127.0.0.1:8000/docs, bạn sẽ thấy trang tài liệu tương tác được sinh tự động. Đây là thứ FastAPI cho bạn miễn phí, và nó luôn khớp với code vì được sinh từ chính code đó.
Định nghĩa dữ liệu bằng Pydantic
Thay vì tự kiểm tra từng trường, bạn mô tả cấu trúc dữ liệu và để Pydantic lo phần còn lại:
from pydantic import BaseModel, Field
from datetime import date
class SachVao(BaseModel):
tieu_de: str = Field(min_length=1, max_length=200)
tac_gia: str
nam_xuat_ban: int = Field(ge=1450, le=2100)
gia: float = Field(gt=0)
class SachRa(SachVao):
id: int
ngay_them: date
Nếu client gửi nam_xuat_ban: 3000, FastAPI tự trả về lỗi 422 kèm mô tả chính xác trường nào sai và sai ở đâu. Bạn không phải viết một dòng validation nào.
Các endpoint cơ bản
from fastapi import HTTPException, status
kho_sach: dict[int, dict] = {}
dem_id = 0
@app.post("/sach", response_model=SachRa, status_code=status.HTTP_201_CREATED)
async def tao_sach(sach: SachVao):
global dem_id
dem_id += 1
ban_ghi = {**sach.model_dump(), "id": dem_id, "ngay_them": date.today()}
kho_sach[dem_id] = ban_ghi
return ban_ghi
@app.get("/sach/{sach_id}", response_model=SachRa)
async def lay_sach(sach_id: int):
if sach_id not in kho_sach:
raise HTTPException(status_code=404, detail="Không tìm thấy sách")
return kho_sach[sach_id]
@app.delete("/sach/{sach_id}", status_code=status.HTTP_204_NO_CONTENT)
async def xoa_sach(sach_id: int):
if kho_sach.pop(sach_id, None) is None:
raise HTTPException(status_code=404, detail="Không tìm thấy sách")
Tham số response_model không chỉ để sinh tài liệu — nó còn lọc dữ liệu trả về. Nếu hàm của bạn vô tình trả thêm trường nhạy cảm, FastAPI sẽ loại bỏ trước khi gửi cho client.
Tham số truy vấn và phân trang
from typing import Annotated
from fastapi import Query
@app.get("/sach", response_model=list[SachRa])
async def danh_sach(
tu_khoa: Annotated[str | None, Query(max_length=50)] = None,
bo_qua: Annotated[int, Query(ge=0)] = 0,
gioi_han: Annotated[int, Query(ge=1, le=100)] = 20,
):
ket_qua = list(kho_sach.values())
if tu_khoa:
ket_qua = [s for s in ket_qua if tu_khoa.lower() in s["tieu_de"].lower()]
return ket_qua[bo_qua : bo_qua + gioi_han]
Giới hạn le=100 cho gioi_han là một chi tiết nhỏ nhưng quan trọng: nó ngăn client yêu cầu một triệu bản ghi trong một lần gọi.
Dependency injection
Khi nhiều endpoint cần chung một thứ — kết nối cơ sở dữ liệu, thông tin người dùng đang đăng nhập — bạn dùng cơ chế dependency:
from fastapi import Depends, Header
async def xac_thuc(x_api_key: Annotated[str, Header()]) -> str:
if x_api_key != "khoa-bi-mat":
raise HTTPException(status_code=401, detail="API key không hợp lệ")
return x_api_key
@app.get("/sach/rieng-tu")
async def du_lieu_rieng(key: Annotated[str, Depends(xac_thuc)]):
return {"du_lieu": "chỉ người có key mới xem được"}
Dependency cũng được thể hiện trong tài liệu tự sinh, nên người dùng API biết ngay endpoint nào cần xác thực.
Chú ý về async
Viết async def không tự động làm code nhanh hơn. Nếu bên trong hàm bạn gọi một thư viện đồng bộ — ví dụ requests hay driver database đồng bộ — bạn sẽ chặn toàn bộ event loop:
# SAI — chặn event loop
@app.get("/cham")
async def cham():
return requests.get("https://api.example.com").json()
# ĐÚNG — dùng thư viện bất đồng bộ
@app.get("/nhanh")
async def nhanh():
async with httpx.AsyncClient() as client:
r = await client.get("https://api.example.com")
return r.json()
Nếu buộc phải dùng thư viện đồng bộ, hãy khai báo endpoint là def thường. FastAPI sẽ tự chạy nó trong threadpool, không chặn event loop.
Triển khai lên môi trường thật
Máy chủ phát triển không dùng cho production. Khi triển khai, chạy qua Uvicorn với nhiều worker:
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Đặt Nginx phía trước để xử lý TLS, nén và phục vụ file tĩnh. Số worker thường lấy bằng số lõi CPU, nhưng hãy đo đạc thực tế thay vì tin vào con số lý thuyết.