Khi endpoint chỉ cần một vài trường đơn giản, bạn không cần tạo Pydantic model — khai báo từng trường bằng Body() là đủ. Với một tham số body duy nhất, FastAPI chờ client gửi đúng giá trị JSON trần; muốn client gửi dạng object có key thì thêm embed=True. Còn khi có từ hai tham số body trở lên, FastAPI tự dùng tên tham số làm key trong JSON và validate từng trường như Query() hay Path().

Vấn đề

Giả sử bạn viết endpoint ghi chú nhanh, chỉ nhận một chuỗi nội dung. Khai báo content: str = Body(...) trông đúng, nhưng client gửi {"content": "ghi chú"} lại nhận 422. Lỗi nằm ở hình dạng body: với một tham số body duy nhất, FastAPI không chờ một object mà chờ đúng giá trị JSON trần — tức là chuỗi "ghi chú". Thông báo 422 chỉ nói trường body sai kiểu, không chỉ rõ hình dạng mong đợi, nên người mới rất dễ tưởng Body() bị hỏng.

from fastapi import Body, FastAPI
app = FastAPI()
@app.post("/note")
def add_note(content: str = Body(...)):
return {"content": content}

Giải pháp

Muốn giữ một tham số duy nhất nhưng cho client gửi dạng object quen thuộc, thêm embed=True vào Body(). Khi đó FastAPI chờ {"content": "ghi chú"} và trả 422 nếu client gửi chuỗi trần — hình dạng body trở nên nhất quán với phần còn lại của API. Cách này hợp với endpoint chỉ nhận đúng một giá trị, ví dụ ghi chú, mã xác nhận hay lý do hủy đơn.

@app.post("/note-embed")
def add_note_embed(content: str = Body(..., embed=True)):
return {"content": content}

Khi endpoint cần hai trường trở lên, bạn không cần embed=True nữa: FastAPI tự dùng tên tham số làm key. Ví dụ endpoint tạo đơn hàng nhận product và quantity, client gửi {"product": "Bút", "quantity": 2} là đủ. Mỗi trường vẫn validate riêng — quantity: int = Body(..., gt=0) chặn số lượng bằng 0 ngay từ cổng API với 422, còn min_length=1 chặn chuỗi rỗng — nên lỗi dữ liệu không lọt vào logic nghiệp vụ. Thiếu một key bất kỳ cũng trả 422, vì mọi tham số Body(...) đều bắt buộc trừ khi bạn đặt giá trị mặc định.

@app.post("/order")
def create_order(
product: str = Body(..., min_length=1),
quantity: int = Body(..., gt=0),
):
return {"product": product, "quantity": quantity}

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

Mẫu này hợp nhất với các endpoint cập nhật nhanh: đổi trạng thái đơn hàng, ghi chú nội bộ, tăng số lượng trong giỏ — những chỗ chỉ cần 2–3 trường và không đáng tạo một model dùng một lần. Nó cũng tiện cho webhook nhận vài trường rời rạc từ dịch vụ ngoài. Quy tắc thực tế: tối đa ba trường đơn thì dùng Body(), nhiều hơn hoặc có trường lồng nhau thì quay về Pydantic model để schema trên /docs vẫn dễ đọc. Trước khi giao API cho frontend, mở /docs kiểm tra hình dạng body FastAPI đã sinh.

Bài viết liên quan

Ba bài cùng họ tham số trong FastAPI:

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

Body() khác gì khai báo một Pydantic model?

Model gom nhiều trường thành một object có tên và tái sử dụng được; Body() khai báo từng trường rời rạc ngay trong chữ ký hàm, nhanh hơn khi chỉ cần 1–3 trường, nhưng schema trên /docs kém gọn.

Khi nào bắt buộc dùng embed=True?

Chỉ khi có đúng một tham số body và bạn muốn client gửi dạng object có key. Với hai tham số trở lên, FastAPI tự embed theo tên nên không cần thêm.

Có trộn Body() với Query() và Path() trong cùng endpoint được không?

Được. FastAPI phân biệt theo khai báo: Path() lấy từ đường dẫn, Query() lấy từ query string, Body() lấy từ body — ba nguồn không xung đột nhau.

Body() validate được như Query() không?

Được, vì Body() nhận cùng bộ tham số validate như Query() và Path(): gt, le, min_length, max_length, pattern — sai ràng buộc nào cũng trả 422 trước khi code trong hàm chạy.

💬 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