Mengendalikan bentuk output API dengan response_model, memakai status code HTTP yang benar untuk tiap operasi, menambahkan response headers, hingga membangun custom response untuk streaming dan file.

Setelah di episode 6 kita menguasai cara menerima data (body, query, path, form), sekarang kita fokus ke arah sebaliknya: output. Cara API mengirimkan respons menentukan pengalaman klien — bentuk JSON yang tidak konsisten, status code yang salah, atau response yang membocorkan field internal adalah masalah yang paling sering dikeluhkan pengguna API.
Mengapa episode ini penting? Karena output API adalah kontrak publik yang akan dipakai tim lain, SDK, dan mobile app. Di sinilah kita memastikan kontrak itu terjaga — secara otomatis, bukan dengan disiplin manual.
response_model adalah deklarasi tipe return yang divalidasi FastAPI sebelum dikirim. Manfaatnya ganda: membatasi field yang keluar dan mendokumentasikan response di OpenAPI.
from pydantic import BaseModel
class UserPublic(BaseModel):
id: int
username: str
class UserInternal(UserPublic):
password_hash: strEndpoint di bawah memakai response_model=UserPublic — meskipun objek asli punya password_hash, field itu tidak akan pernah keluar:
from fastapi import FastAPI
from app.schemas import UserInternal, UserPublic
app = FastAPI()
USERS = {
1: UserInternal(id=1, username="budi", password_hash="hash_rahasia"),
}
@app.get("/users/{user_id}", response_model=UserPublic)
def get_user(user_id: int) -> UserInternal:
return USERS[user_id]Inilah pola internal vs public schema: simpan objek lengkap (termasuk hash password) di dalam handler, tapi biarkan FastAPI memfilter sesuai response_model. Tidak ada cara yang lebih mudah untuk mencegah kebocoran data.
Important
response_model memvalidasi output, sama seperti request body divalidasi saat input. Kalau handler mengembalikan objek yang tidak cocok dengan response_model (misal field kurang), FastAPI melempar error saat runtime — lebih baik terdeteksi sekarang daripada klien menerima JSON yang rusak.
Status code adalah bahasa universal HTTP. FastAPI memakainya lewat parameter status_code pada dekorator:
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(name: str) -> dict:
return {"name": name}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int) -> None:
return NoneGunakan status module (bukan angka mentah) — lebih terbaca dan bebas typo. Peta status yang paling sering dipakai:
| Status | Kapan | Catatan |
|---|---|---|
200 OK | GET/PUT sukses | Default FastAPI |
201 Created | POST membuat resource | Sertakan body resource baru |
204 No Content | DELETE sukses | Tanpa body — return None |
404 Not Found | Resource tak ada | Lihat HTTPException episode 17 |
422 Unprocessable Entity | Validasi gagal | Otomatis dari Pydantic |
307 | Redirect trailing slash | Otomatis jika path beda slash |
Tip
Untuk 204 No Content, jangan tambahkan response_model dan return None — FastAPI secara otomatis tidak mengirim body. Mengirim body pada 204 akan membuat klien bingung karena HTTP melarang body pada status tersebut.
Kadang perlu menyisipkan metadata ke header — misalnya X-Process-Time untuk observability atau Location untuk resource baru:
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/items/", status_code=201)
def create_item(item: str, response: Response) -> dict:
response.headers["Location"] = "/items/1"
response.headers["X-Created-By"] = "fastapi-lab"
return {"name": item}Menyuntikkan parameter bertipe Response ke handler memberi kalian akses langsung ke objek response Starlette — cara idiomatis menambah header tanpa meninggalkan deklarasi normal.
Tidak semua response adalah JSON. FastAPI menyediakan kelas response khusus:
| Class | Penggunaan |
|---|---|
JSONResponse | Default; body JSON |
HTMLResponse | Halaman HTML (minimal) |
PlainTextResponse | Teks polos |
RedirectResponse | Redirect URL |
FileResponse | Mengirim file (episode 15) |
StreamingResponse | Streaming data / file besar (episode 23) |
Contoh streaming generator — pola yang nanti menjadi fondasi LLM streaming:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
def angka():
for i in range(5):
yield f"{i}\n"
@app.get("/numbers")
def read_numbers() -> StreamingResponse:
return StreamingResponse(angka(), media_type="text/plain")StreamingResponse menerima iterator dan mengirim hasilnya bertahap — klien mulai menerima data sebelum seluruh generator selesai. Ini esensial untuk payload besar dan, di episode 23, untuk streaming token LLM.
| Yang dideklarasikan | Efek |
|---|---|
response_model=UserPublic | Filter field + validasi + dokumentasi |
status_code=201 | Status HTTP di docs dan runtime |
response: Response parameter | Akses header di dalam handler |
StreamingResponse return | Kirim data bertahap |
| Pitfall | Solusi |
|---|---|
| Menaruh field sensitif di model response | Pisahkan UserPublic vs UserInternal |
| Body pada status 204 | Return None, tanpa response_model |
response_model tidak cocok dengan return | Sinkronkan — FastAPI akan error saat runtime |
| Memakai angka status mentah | Gunakan status.HTTP_XXX |
Inti yang harus dibawa pulang:
response_model = kontrak output: filter field, validasi, dan dokumentasi dalam satu deklarasi.status.HTTP_*; 201 untuk create, 204 untuk delete.Response memberikan akses header.StreamingResponse untuk data bertahap — fondasi streaming LLM di episode 23.Di episode 8 selanjutnya kita akan membahas dependency injection — cara FastAPI merapikan logika bersama. Kalian akan belajar Depends, sub-dependencies, class dependencies, dan global dependencies, serta pola Annotated yang direkomendasikan FastAPI 0.141. Ini akan mengubah cara kalian menulis endpoint selamanya!