Mục lục
Mục tiêu bài học
Sau bài này bạn sẽ:
- Cài FastAPI và uvicorn vào virtual environment đúng cách.
- Viết được 3 loại endpoint GET cơ bản: root, path param, query param.
- Hiểu tại sao FastAPI tự serialize dict thành JSON và type hint dùng để làm gì.
- Chạy dev server với hot reload, truy cập Swagger UI.
- Biết lỗi 422 xuất hiện khi nào và JSON error response trông như thế nào.
Setup môi trường
Yêu cầu Python
FastAPI 0.100+ yêu cầu Python ≥ 3.7. Pydantic v2 (bundled từ FastAPI 0.100+) cũng yêu cầu Python ≥ 3.7. Trên thực tế nên dùng Python 3.10+ để có union type syntax X | Y gọn hơn.
python3 --version
# Python 3.11.x hoặc 3.12.x là ổn
Tạo virtual environment
Luôn dùng venv cho từng project để tránh conflict phiên bản giữa các project.
# Linux / macOS
python3 -m venv .venv
source .venv/bin/activate
# Windows (PowerShell)
python -m venv .venv
.venv\Scripts\activate
Prompt sẽ thay đổi thành (.venv) khi đã activate.
Cài FastAPI
Có hai cách:
# Cách 1 (khuyến nghị): bundle đầy đủ
# "fastapi[standard]" kéo theo uvicorn[standard], httpx (test client),
# python-multipart (upload file), email-validator — đủ dùng cho hầu hết bài trong module này
pip install "fastapi[standard]"
# Cách 2: minimal — chỉ cần chạy server
pip install fastapi "uvicorn[standard]"
uvicorn[standard] thêm uvloop (event loop nhanh hơn trên Linux/macOS) và websockets. Bỏ [standard] vẫn chạy được nhưng chậm hơn một chút.
Verify
fastapi --version
# FastAPI CLI version: 0.0.x
# FastAPI version: 0.11x.x
uvicorn --version
# Running uvicorn 0.29.x with CPython 3.11.x on ...
Nếu lệnh fastapi không tìm thấy, kiểm tra lại venv đã được activate chưa.
File main.py — 3 endpoint đầu tiên
Tạo file main.py ở root project:
from fastapi import FastAPI
app = FastAPI()
# Endpoint 1: GET /
@app.get("/")
def read_root():
return {"hello": "world"}
# Endpoint 2: GET /items/{item_id}
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
# Endpoint 3: GET /search
@app.get("/search")
def search(q: str = ""):
return {"query": q, "results": []}
Giải thích từng phần
app = FastAPI()
Tạo instance ASGI application. Uvicorn sẽ import đối tượng này theo cú pháp main:app (module main, attribute app). Nếu đặt tên biến khác, phải truyền tên đó vào lệnh uvicorn.
Tại sao return dict mà ra JSON?
FastAPI tự wrap response bằng JSONResponse khi hàm trả về dict, list, Pydantic model, hoặc kiểu primitive. Không cần gọi json.dumps() hay set header Content-Type thủ công. Đây là hành vi mặc định, được implement bên trong fastapi.routing.APIRoute.
Type hint item_id: int dùng để làm gì?
FastAPI parse type hint của path param và query param để:
- Tự động convert: string từ URL (
"42") được cast thànhinttrước khi truyền vào hàm. - Validate: nếu cast thất bại (ví dụ
"abc"), FastAPI trả về HTTP 422 kèm JSON chi tiết lỗi. - Generate OpenAPI schema: type hint được reflect vào spec, Swagger UI hiển thị đúng kiểu dữ liệu.
Query param với default value
Bất kỳ tham số hàm nào không xuất hiện trong path template {...} được coi là query param. Gán giá trị mặc định (q: str = "") làm param optional. Bỏ default thì FastAPI yêu cầu client phải truyền.
# Optional query param — client có thể bỏ qua
def search(q: str = ""):
...
# Required query param — thiếu → 422
def search(q: str):
...
Chạy dev server
Dùng FastAPI CLI (từ 0.103+)
fastapi dev main.py
Output khi khởi động thành công:
INFO Using path main.py
INFO Importing from /path/to/project
╭─ Python module file ─╮
│ │
│ 🐍 main.py │
│ │
╰───────────────────────╯
INFO Importing module main
INFO Found importable FastAPI app
╭─ Importable FastAPI app ─╮
│ │
│ from main import app │
│ │
╰───────────────────────────╯
INFO Using import string main:app
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
│ Serving at: http://127.0.0.1:8000 │
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ fastapi run │
│ │
╰───────────────────────────────────────────────────────╯
INFO: Will watch for changes in these directories: ['/path/to/project']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [12345] using WatchFiles
fastapi dev tự bật hot reload — sửa code, lưu file, server reload tự động. Không cần truyền --reload.
Dùng uvicorn trực tiếp
uvicorn main:app --reload
Hai lệnh này tương đương cho môi trường dev. fastapi dev là wrapper, bên trong gọi uvicorn với --reload và một số config mặc định khác.
Truy cập
http://127.0.0.1:8000/— endpoint root, trả{"hello":"world"}http://127.0.0.1:8000/docs— Swagger UIhttp://127.0.0.1:8000/redoc— ReDoc UIhttp://127.0.0.1:8000/openapi.json— raw OpenAPI 3.1 spec
Swagger UI và ReDoc tự động
Mở http://127.0.0.1:8000/docs sẽ thấy Swagger UI liệt kê 3 endpoint đã viết, với ô nhập param và nút "Try it out".
Tại sao FastAPI có thể tạo docs tự động?
FastAPI đọc thông tin từ 3 nguồn khi ứng dụng khởi động:
- Decorator
@app.get("/path")— xác định HTTP method và URL pattern. - Type hint của tham số hàm — xác định kiểu dữ liệu, required hay optional.
- Pydantic model (nếu có ở request/response body) — xác định schema JSON.
Từ đó FastAPI generate OpenAPI 3.1 spec dưới dạng JSON, rồi render Swagger UI và ReDoc từ spec này. Swagger UI và ReDoc chỉ là frontend đọc file /openapi.json.
Bạn có thể xem raw spec bằng curl http://127.0.0.1:8000/openapi.json hoặc trực tiếp trên browser. Spec này cũng là input cho codegen tool (openapi-generator, kiota...) nếu cần generate client SDK.
Test bằng curl
Endpoint root
curl http://127.0.0.1:8000/
{"hello":"world"}
Path param hợp lệ
curl http://127.0.0.1:8000/items/42
{"item_id":42}
Giá trị trả về là số nguyên 42, không phải string "42" — FastAPI đã convert và serialize đúng kiểu.
Query param
curl "http://127.0.0.1:8000/search?q=fastapi"
{"query":"fastapi","results":[]}
Path param sai type — lỗi 422
curl http://127.0.0.1:8000/items/abc
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc",
"url": "https://errors.pydantic.dev/2.x/v/int_parsing"
}
]
}
HTTP 422 Unprocessable Entity — status code tiêu chuẩn cho validation error. Response có cấu trúc cố định: mảng detail, mỗi phần tử gồm type, loc (vị trí param), msg, input. Pydantic v2 generate error này, không phải FastAPI tự viết. Cấu trúc này nhất quán với mọi loại validation error trong ứng dụng.
Production mode
Phần này chỉ giới thiệu để biết sự khác biệt. Deploy thực tế sẽ được đào sâu ở Module 6.
fastapi run
fastapi run main.py
Tương đương uvicorn main:app (không có --reload). Single process, phù hợp với container chạy 1 instance.
uvicorn với nhiều worker
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Mỗi worker là một process Python độc lập (fork từ process cha). Dùng khi bạn có logic CPU-bound và muốn tận dụng nhiều CPU core. Lưu ý:
- Mỗi worker load model riêng — nếu model nặng (vài GB),
--workers 4nhân bộ nhớ RAM lên 4 lần. - Không dùng
--workerscùng với--reload— hai flag xung đột. - Với I/O-bound (gọi LLM API bên ngoài), tăng concurrency bằng async thay vì tăng worker. Bài 4 sẽ đi vào vấn đề này.
Gunicorn + uvicorn worker
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
Pattern phổ biến trên bare metal / VPS. Gunicorn quản lý lifecycle của worker, uvicorn xử lý ASGI. Trên môi trường containerized (Docker + Kubernetes), thường dùng 1 process/container rồi scale ngang bằng replica thay vì dùng Gunicorn.
Common pitfalls
1. Quên --reload khi dùng uvicorn trực tiếp
# Sai — phải restart thủ công sau mỗi lần sửa code
uvicorn main:app
# Đúng cho dev
uvicorn main:app --reload
# Hoặc
fastapi dev main.py
2. Path trùng — endpoint cụ thể bị che bởi path param
from fastapi import FastAPI
app = FastAPI()
# Endpoint này được định nghĩa TRƯỚC
@app.get("/items/{item_id}")
def read_item(item_id: str):
return {"item_id": item_id}
# Endpoint này sẽ KHÔNG BAO GIỜ được match
# Vì "/items/all" match pattern trên với item_id="all"
@app.get("/items/all")
def read_all():
return {"items": []}
FastAPI match route theo thứ tự định nghĩa. Phải đặt endpoint cụ thể (/items/all) trước endpoint có path param (/items/{item_id}):
@app.get("/items/all") # Định nghĩa trước
def read_all():
return {"items": []}
@app.get("/items/{item_id}") # Định nghĩa sau
def read_item(item_id: int):
return {"item_id": item_id}
3. Tên biến app không khớp với lệnh uvicorn
# main.py
from fastapi import FastAPI
# Đặt tên khác
api = FastAPI()
# Sai — uvicorn tìm attribute "app" trong module "main"
uvicorn main:app
# Đúng — trỏ đúng tên biến
uvicorn main:api
Convention phổ biến là dùng app = FastAPI() vì nhiều tool và deployment script mặc định trỏ vào main:app.
4. Activate sai venv
Nếu có nhiều project trên máy, kiểm tra venv đang active bằng:
which python
# Phải là: /path/to/project/.venv/bin/python
# Không phải: /usr/bin/python hoặc /usr/local/bin/python
Tóm tắt
- Cài
pip install "fastapi[standard]"trong venv — kéo theo uvicorn và httpx test client. app = FastAPI()là ASGI instance, tên biến phải khớp với lệnh chạy server.- Decorator
@app.get("/path")đăng ký route; FastAPI tự serialize dict thành JSON response. - Type hint trên tham số hàm → auto convert, validate, generate OpenAPI schema.
- Path param nằm trong
{}; query param là tham số hàm còn lại; default value làm optional. fastapi dev main.pychạy dev server với hot reload;fastapi run main.pycho production.- Swagger UI tại
/docs, ReDoc tại/redoc— tự động từ OpenAPI 3.1 spec. - Type mismatch trên path/query param → HTTP 422 với JSON error detail từ Pydantic v2.
- Đặt route cụ thể trước route có path param để tránh bị che.
