Nhiều API có tham số đường dẫn chỉ nhận vài giá trị cố định, ví dụ trạng thái đơn hàng: pending, shipped hoặc delivered. Thay vì nhận str rồi kiểm tra tay bằng if/else, bạn khai báo tham số kiểu Enum kế thừa str và Enum — FastAPI tự chặn giá trị lạ bằng lỗi 422, tự sinh tài liệu với danh sách lựa chọn, và code trong endpoint không còn nhánh xử lý giá trị sai.

Vấn đề

Giả sử bạn viết endpoint liệt kê đơn hàng theo trạng thái. Cách quen thuộc là nhận status kiểu str rồi rẽ nhánh kiểm tra từng giá trị:

@app.get("/orders/{status}")
def list_orders(status: str):
if status == "pending":
return {"orders": ["#1001", "#1002"]}
elif status == "shipped":
return {"orders": ["#1003"]}
elif status == "delivered":
return {"orders": ["#1004", "#1005"]}
return {"error": "Trạng thái không hợp lệ"}

Cách này có ba điểm yếu. Thứ nhất, giá trị lạ vẫn đi qua endpoint rồi mới bị chặn, và client nhận về một JSON lỗi tự chế thay vì lỗi chuẩn của framework. Thứ hai, tài liệu tự sinh chỉ ghi status là string, người gọi API không biết có những giá trị nào hợp lệ nếu không đọc code. Thứ ba, mỗi endpoint có tham số dạng danh mục lại phải lặp lại đoạn if/else giống nhau, càng về sau càng khó bảo trì.

Giải pháp

Khai báo một Enum kế thừa str và Enum, rồi dùng nó làm kiểu của tham số:

from enum import Enum
from fastapi import FastAPI
class OrderStatus(str, Enum):
PENDING = "pending"
SHIPPED = "shipped"
DELIVERED = "delivered"
app = FastAPI()
ORDERS = {
"pending": ["#1001", "#1002"],
"shipped": ["#1003"],
"delivered": ["#1004", "#1005"],
}
@app.get("/orders/{status}")
def list_orders(status: OrderStatus):
return {"status": status.value, "orders": ORDERS[status.value]}

Kế thừa str giúp giá trị Enum vẫn là chuỗi khi trả về JSON, còn kế thừa Enum cho FastAPI biết danh sách giá trị hợp lệ. Trong endpoint, status đã chắc chắn là một trong ba giá trị, nên bạn dùng trực tiếp status.value mà không cần kiểm tra thêm.

Kiểm chứng bằng TestClient: GET /orders/pending trả 200 kèm đúng danh sách đơn; GET /orders/cancelled trả 422 vì giá trị không có trong Enum; GET /orders/PENDING cũng trả 422 vì so sánh phân biệt hoa thường. Tài liệu tại /docs hiển thị status dưới dạng danh sách lựa chọn, người gọi chỉ cần bấm chọn thay vì đoán.

Áp dụng vào dự án của bạn

Mẫu này dùng được cho mọi tham số chỉ nhận tập giá trị cố định: trạng thái đơn hàng, loại tài khoản, ngôn ngữ giao diện, mức độ ưu tiên. Cùng một Enum có thể dùng lại cho query parameter và cho nhiều endpoint khác nhau, và khi thêm giá trị mới bạn chỉ sửa một chỗ. Nếu muốn mô tả rõ hơn trong tài liệu, kết hợp thêm Path(description=…) cho tham số đó.

Bài viết liên quan

Câu hỏi thường gặp

Enum khác gì Literal?

Literal cũng giúp FastAPI chặn giá trị lạ, nhưng nó chỉ là gợi ý kiểu gắn tại chỗ. Enum là một kiểu có tên, tái sử dụng được cho nhiều endpoint, có thể gắn thêm phương thức, và hiển thị rõ trong tài liệu dưới dạng một schema riêng.

Enum số nguyên dùng được không?

Được. Khai báo class Priority(int, Enum) với các giá trị 1, 2, 3 — FastAPI validate số nguyên và tài liệu hiển thị đúng kiểu integer thay vì string.

Giá trị Enum có phân biệt hoa thường không?

Có. “PENDING” không khớp “pending” nên FastAPI trả 422. Nếu muốn chấp nhận cả hai dạng, bạn tự chuẩn hóa đầu vào trước khi so sánh, hoặc định nghĩa thêm giá trị viết hoa trong Enum.

Enum dùng cho query parameter được không?

Được, cách khai báo giống hệt: def search(status: OrderStatus = Query(…)). FastAPI validate query param bằng cùng một Enum và hiển thị danh sách lựa chọn trong tài liệu.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo