Menguasai berbagai cara menerima input di FastAPI: request body berbasis Pydantic model, parameter body tunggal, embed untuk struktur JSON tertentu, kombinasi path, query dan body sekaligus, serta Form data untuk aplikasi x-www-form-urlencoded.

Setelah di episode 5 kita membangun Pydantic models dengan validasi penuh, sekarang kita pelajari cara-cara FastAPI menerima data dari klien. Selama ini kita hanya memakai satu model per request — padahal di dunia nyata, satu endpoint sering menerima kombinasi: parameter di path, filter di query, dan data kompleks di body.
Mengapa episode ini penting? Karena kegagalan memahami cara kerja body akan menghasilkan API yang sulit dipakai klien — misalnya struktur JSON yang tidak intuitif, atau endpoint yang menolak form login padahal frontend mengirimnya. FastAPI memberi kontrol penuh atas bentuk request body; kita pelajari tiap opsi sekarang.
Cara paling idiomatis — deklarasikan satu parameter bertipe model, dan FastAPI mengekspektasi seluruh body sesuai model tersebut:
from fastapi import FastAPI
from app.schemas import ItemCreate
app = FastAPI()
@app.post("/items/")
def create_item(item: ItemCreate) -> dict:
return {"name": item.name, "price": item.price}Klien harus mengirim JSON yang persis bentuk ItemCreate:
{"name": "Keyboard", "price": 500000}Jika klien mengirim field tambahan — {"name": "Keyboard", "price": 500000, "stock": 5} — perilaku default Pydantic 2.x adalah mengabaikannya (ignore extra). Ini sering mengejutkan, jadi pastikan kalian tahu: atur model_config = ConfigDict(extra="forbid") pada model jika ingin menolak field tak dikenal.
Satu endpoint bisa menerima ketiga sumber sekaligus. FastAPI menentukan asal parameter dari tipe dan deklarasinya:
@app.put("/items/{item_id}")
def update_item(
item_id: int, # dari path
q: str | None = None, # dari query
item: ItemCreate, # dari body
) -> dict:
return {"item_id": item_id, "q": q, "item": item.model_dump()}Aturannya konsisten dari episode 4: parameter yang namanya cocok dengan {kurung_kurawal} di path → path param; parameter primitif lainnya → query param; parameter bertipe Pydantic model → body. Tidak ada konflik karena tipe menentukan asal.
Tip
Urutan deklarasi bebas, tapi untuk keterbacaan ikuti konvensi: path param, lalu query, lalu body — persis seperti URL-nya: /path?query + body.
FastAPI juga menerima parameter primitif sebagai body langsung:
from fastapi import Body
@app.put("/items/")
def create_item(name: str = Body(...)) -> dict:
return {"name": name}Klien mengirim body "Keyboard" (JSON string), bukan {"name": "Keyboard"}. Gunakan pola ini hanya saat memang dibutuhkan — untuk API publik, bentuk objek hampir selalu lebih jelas dan lebih mudah dievolusi.
Default FastAPI: ketika sebuah fungsi punya satu parameter bertipe model, seluruh body dipetakan langsung ke model — body {"name": ...} cocok dengan item: ItemCreate. Tapi ketika ada dua parameter bertipe model, FastAPI butuh cara membedakan keduanya, dan di sinilah Body(embed=True) bekerja:
from fastapi import FastAPI, Body
from app.schemas import ItemCreate, Tag
app = FastAPI()
@app.post("/items/with-tag/")
def create_with_tag(
item: ItemCreate = Body(embed=True),
tag: Tag = Body(embed=True),
) -> dict:
return {"item": item.model_dump(), "tag": tag.model_dump()}Body yang diharapkan klien:
{
"item": {"name": "Keyboard", "price": 500000},
"tag": {"name": "input"}
}embed=True membungkus tiap parameter body di bawah kunci-nya sendiri. Tanpa embed, FastAPI tidak bisa membedakan dua body — ini salah satu detail FastAPI yang paling sering bikin pusing pemula.
Note
Aturan cepat: satu parameter body → default langsung (tanpa embed). Dua atau lebih parameter body → tiap parameter perlu Body(embed=True). Kalau kalian melihat schema body aneh di /docs, kemungkinan besar masalahnya ada di sini.
FastAPI menangani form HTML dan data application/x-www-form-urlencoded melalui Form. Ini penting untuk endpoint login klasik dan aplikasi yang bukan SPA:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
def login(
username: str = Form(...),
password: str = Form(...),
) -> dict[str, str]:
return {"username": username, "message": "login berhasil"}Perbedaan penting yang wajib dihafal:
Form(...) membaca data dari body urlencoded — klien mengirim username=...&password=....Form, package python-multipart harus diinstall: pip install python-multipart — tanpa itu, FastAPI error saat import.curl -s -X POST http://127.0.0.1:8000/login/ \
-d "username=budi&password=rahasia"Warning
Jangan mengirim data melalui Form untuk payload kompleks — form urlencoded hanya mendukung pasangan key-value sederhana. Untuk struktur bertingkat tetap gunakan JSON body dengan model. Dan ingat: contoh login di sini hanya demo parsing; keamanan auth yang benar dibahas di episode 12.
| Jenis | Sumber data | Kapan dipakai |
|---|---|---|
Model / Body | JSON body | Data terstruktur, API utama |
Query | URL ?key=value | Filter, pagination |
Path | Segmen URL | Identitas resource |
Form | application/x-www-form-urlencoded | Form HTML, login klasik |
Header / Cookie | HTTP header / cookie | Metadata, token (episode 12) |
| Pitfall | Solusi |
|---|---|
Dua body model tanpa embed=True | Schema body aneh di /docs; tambahkan embed=True |
Form tanpa python-multipart | Import error; install python-multipart |
| Field ekstra diam-diam diabaikan | ConfigDict(extra="forbid") pada model |
Mengirim JSON ke endpoint Form | Ganti klien: kirim x-www-form-urlencoded |
Inti yang harus dibawa pulang:
Body(embed=True).Form untuk urlencoded; butuh python-multipart.Header/Cookie tersedia sebagai jenis data lain yang kita pakai di episode 12.Di episode 7 selanjutnya kita akan membahas response model & status code — memastikan bentuk output tetap terjaga dengan response_model, memakai status code yang tepat untuk tiap operasi, menambahkan response headers, sampai membangun custom response. Output yang rapi adalah wajah API kalian di depan klien!