Baris status adalah bagian pertama yang dibaca mesin dari setiap response — kode 201 memberi tahu klien bahwa resource baru tercipta, sedangkan 200 hanya bilang “sukses” tanpa detail. Kebiasaan lama mengembalikan 200 untuk segala hal lalu menaruh status di dalam body ("{"sukses": false}") memaksa klien mem-parsing body dulu baru tahu request-nya gagal atau tidak — mahal dan rawan salah. Prinsip chapter ini sederhana: status line diisi kode yang benar, dan error mengikuti satu bentuk konsisten. Kita tetap bekerja pada API manajemen tugas dengan endpoint yang semakin lengkap.

Keluarga Status Code #

Lima keluarga status code membagi tanggung jawab yang jelas:

KeluargaMaknaKode yang sering dipakai API
2xxsukses200 OK, 201 Created, 204 No Content
3xxredirect / cache301 Moved Permanently, 304 Not Modified
4xxkesalahan klien400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Unprocessable Entity
5xxkesalahan server500 Internal Server Error, 503 Service Unavailable

Aturan praktis untuk CRUD pada API tugas: 200 untuk GET dan PUT yang mengembalikan data, 201 untuk POST yang membuat resource, 204 untuk DELETE yang berhasil, 404 untuk resource yang tidak ada, dan 422 untuk body yang gagal validasi Pydantic (sudah kita lihat di chapter Request Models dan Validation).

Status Sukses dan HTTPException untuk Error #

Parameter status_code pada decorator mengatur kode sukses, sedangkan HTTPException menangani error yang bisa diduga saat runtime:

python
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel


class TugasBaru(BaseModel):
    judul: str


app = FastAPI()
tugas_db = {1: {"id": 1, "judul": "Belajar Python", "selesai": False}}


@app.get("/tugas/{task_id}")
def detail_tugas(task_id: int):
    if task_id not in tugas_db:
        raise HTTPException(status_code=404, detail=f"Tugas {task_id} tidak ditemukan")
    return tugas_db[task_id]


@app.post("/tugas", status_code=201)
def buat_tugas(tugas: TugasBaru):
    if any(t["judul"] == tugas.judul for t in tugas_db.values()):
        raise HTTPException(status_code=400, detail=f"Judul '{tugas.judul}' sudah dipakai")
    id_baru = max(tugas_db) + 1
    tugas_db[id_baru] = {"id": id_baru, "judul": tugas.judul, "selesai": False}
    return tugas_db[id_baru]


@app.delete("/tugas/{task_id}", status_code=204)
def hapus_tugas(task_id: int):
    if task_id not in tugas_db:
        raise HTTPException(status_code=404, detail=f"Tugas {task_id} tidak ditemukan")
    del tugas_db[task_id]


client = TestClient(app)

print(client.get("/tugas/1").status_code, client.get("/tugas/1").json())
r = client.get("/tugas/99")
print(r.status_code, r.json())

r = client.post("/tugas", json={"judul": "Belajar Python"})
print(r.status_code, r.json())

r = client.post("/tugas", json={"judul": "Belajar status code"})
print(r.status_code, r.json())

r = client.delete("/tugas/1")
print(r.status_code, repr(r.text))
r = client.delete("/tugas/1")
print(r.status_code, r.json())

Output yang Dihasilkan:

text
200 {'id': 1, 'judul': 'Belajar Python', 'selesai': False}
404 {'detail': 'Tugas 99 tidak ditemukan'}
400 {'detail': "Judul 'Belajar Python' sudah dipakai"}
201 {'id': 2, 'judul': 'Belajar status code', 'selesai': False}
204 ''
404 {'detail': 'Tugas 1 tidak ditemukan'}

Mari kita telaah baris-baris kuncinya:

  1. Baris 17 (raise HTTPException(status_code=404, ...)): raise menghentikan fungsi saat itu juga; FastAPI menangkap exception ini dan mengirim response 404 dengan body {"detail": ...} — tidak perlu menulis try/except di kode rute.
  2. Baris 21 (@app.post("/tugas", status_code=201)): POST sukses mengembalikan 201 Created, bukan 200 — resource baru saja tercipta dan body masih memuat data tugasnya.
  3. Baris 23-24 (raise HTTPException(status_code=400, ...)): 400 Bad Request dipakai untuk request yang sah secara struktur tetapi melanggar aturan bisnis — di sini judul duplikat. Bedakan dengan 422: yang satu ditolak aturan domain kita, yang lain gagal validasi Pydantic.
  4. Baris 30 (status_code=204): 204 No Content berarti “sukses tanpa ada yang perlu dikirim kembali” — perhatikan output '': body benar-benar kosong, bukan "null".
  5. Baris 49-52 (dua panggilan DELETE): pemanggilan pertama mengembalikan 204, pemanggilan kedua 404 karena tugas sudah tiada. DELETE tetap idempotent — efeknya sama (tugas 1 tidak ada) — meskipun kode statusnya berbeda.

Custom Exception Handler untuk Konsistensi #

HTTPException menghasilkan body berbentuk {"detail": ...}. Bentuk ini sah, tetapi beberapa tim ingin format error yang lebih kaya atau seragam untuk seluruh domain — misalnya kode error internal tugas_tidak_ditemukan. FastAPI memungkinkan kita mendefinisikan exception sendiri beserta handler-nya:

python
from fastapi import Request
from fastapi.responses import JSONResponse


class TugasTidakDitemukan(Exception):
    def __init__(self, task_id: int):
        self.task_id = task_id


@app.exception_handler(TugasTidakDitemukan)
async def handler_tugas_tidak_ditemukan(request: Request, exc: TugasTidakDitemukan):
    return JSONResponse(
        status_code=404,
        content={"error": "tugas_tidak_ditemukan", "task_id": exc.task_id},
    )


@app.get("/laporan/tugas/{task_id}")
def laporan_tugas(task_id: int):
    if task_id not in tugas_db:
        raise TugasTidakDitemukan(task_id)
    return {"task_id": task_id, "status": "aktif"}


print(client.get("/laporan/tugas/99").status_code,
      client.get("/laporan/tugas/99").json())

Output yang Dihasilkan:

text
404 {'error': 'tugas_tidak_ditemukan', 'task_id': 99}

Handler terdaftar lewat decorator @app.exception_handler(TugasTidakDitemukan); FastAPI memanggil handler tersebut setiap kali exception itu terbangkitkan dari rute mana pun, lalu JSONResponse menentukan status code dan body secara penuh. Fungsi handler ditulis async def mengikuti konvensi FastAPI untuk exception handler — materi async/await dibahas pada Section Asynchronous Programming.

Dengan sukses dan error beres, endpoint berikutnya yang perlu kita rapikan adalah daftar tugas yang terus membesar — uraian pagination ada di chapter Pagination, Filtering, dan Sorting.