Pintu masuk sudah dijaga kontrak request; kini sisi keluar yang harus dikunci. Data internal aplikasi hampir selalu lebih kaya daripada yang boleh dilihat klien: baris pengguna menyimpan password, baris tugas menyimpan catatan internal, dan field seperti itu tidak boleh bocor lewat response JSON. Mengandalkan kehati-hatian saat menyusun dictionary secara manual itu rapuh — satu field lupa dihapus, dan data sensitif terkirim ke seluruh dunia. FastAPI menyediakan pertahanan deklaratif untuk ini: parameter response_model pada path operation menyaring output sehingga hanya field yang dideklarasikan yang lolos ke klien.
Memfilter Field dengan response_model #
Perhatikan API pendaftaran pengguna berikut. Penyimpanan internal (pengguna_db) memang berisi field password, tetapi response JSON tidak boleh memuatnya:
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
class PenggunaCreate(BaseModel):
nama: str
email: str
password: str
class PenggunaResponse(BaseModel):
id: int
nama: str
email: str
app = FastAPI()
pengguna_db = [
{"id": 1, "nama": "Budi", "email": "[email protected]", "password": "sandi-budi"},
{"id": 2, "nama": "Siti", "email": "[email protected]", "password": "sandi-siti"},
]
@app.post("/pengguna", response_model=PenggunaResponse, status_code=201)
def daftar_pengguna(pengguna: PenggunaCreate):
return {"id": 7, **pengguna.model_dump()}
@app.get("/pengguna", response_model=list[PenggunaResponse])
def daftar_semua_pengguna():
return pengguna_db
client = TestClient(app)
r = client.post(
"/pengguna",
json={"nama": "Budi", "email": "[email protected]", "password": "sandi-rahasia"},
)
print(r.status_code, r.json())
print(client.get("/pengguna").json())Output yang Dihasilkan:
201 {'id': 7, 'nama': 'Budi', 'email': '[email protected]'}
[{'id': 1, 'nama': 'Budi', 'email': '[email protected]'}, {'id': 2, 'nama': 'Siti', 'email': '[email protected]'}]Mari kita telaah mekanismenya:
- Baris 12 (
class PenggunaResponse(BaseModel)): model response hanya memuatid,nama, danemail— fieldpasswordsengaja tidak dideklarasikan. - Baris 26 (
response_model=PenggunaResponse): sebelum response dikirim, FastAPI melewatkan nilai kembalian fungsi melalui model ini; setiap field di luar deklarasi dibuang. - Baris 28 (
return {"id": 7, **pengguna.model_dump()}): dictionary yang dikembalikan oleh fungsi masih memuatpassword— penyaringan baru terjadi setelahnya, tepat sebelum serialisasi JSON. Output pertama membuktikannya: hanya tiga field yang sampai ke klien. - Baris 31 (
response_model=list[PenggunaResponse]): untuk koleksi, gunakanlist[...]agar setiap elemen disaring dengan model yang sama — output kedua memperlihatkan dua pengguna tanpa satu punpassword.
Memisahkan Model Input dan Output #
Contoh di atas memakai dua model untuk resource yang sama, dan pemisahan ini disengaja:
| Model | Arah | Isi | Contoh |
|---|---|---|---|
PenggunaCreate | klien → server | field yang dikirim klien, termasuk password | request body POST |
PenggunaResponse | server → klien | field yang aman dilihat, tanpa password | response JSON |
Model input dan output punya konsumen dan kebutuhan yang berbeda, jadi menggabungkannya dalam satu class hanya menggandakan risiko: class gabungan yang dipakai sebagai response otomatis mengizinkan klien membaca kembali field rahasia. Dengan dua model, menambah field internal cukup dilakukan pada model input tanpa menyentuh kontrak response.
Bonus Parameter status_code #
Perhatikan parameter status_code=201 pada decorator POST di atas: nilai kembalian fungsi tetap diproses response_model, sementara baris status response berubah dari default 200 OK menjadi 201 Created — sesuai semantik yang kita tetapkan di chapter Desain REST API. Parameter ini juga berlaku untuk method lain, misalnya status_code=204 pada DELETE yang akan kita praktikkan di chapter Status Codes dan Error Responses.