Danh sách bài viết

Bài 2: Cài đặt FastAPI và endpoint đầu tiên

Cài đặt FastAPI trong virtual environment, viết 3 endpoint GET cơ bản với path param và query param, chạy dev server, và kiểm tra Swagger UI. Kèm demo lỗi 422 khi type mismatch.

27/05/2026
1 lượt xem
1

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.
2

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.

3

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 để:

  1. Tự động convert: string từ URL ("42") được cast thành int trước khi truyền vào hàm.
  2. Validate: nếu cast thất bại (ví dụ "abc"), FastAPI trả về HTTP 422 kèm JSON chi tiết lỗi.
  3. 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):
    ...
4

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 UI
  • http://127.0.0.1:8000/redoc — ReDoc UI
  • http://127.0.0.1:8000/openapi.json — raw OpenAPI 3.1 spec
5

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:

  1. Decorator @app.get("/path") — xác định HTTP method và URL pattern.
  2. Type hint của tham số hàm — xác định kiểu dữ liệu, required hay optional.
  3. 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.

6

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.

7

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 4 nhân bộ nhớ RAM lên 4 lần.
  • Không dùng --workers cù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.

8

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
9

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.py chạy dev server với hot reload; fastapi run main.py cho 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.