Desain API Resource yang Tidak Bikin Frontend Menderita
Setiap kali saya masuk ke proyek Laravel yang API-nya "berantakan", masalahnya jarang di query atau performa. Masalahnya ada di response JSON yang tidak konsisten: kadang field created_at muncul, kadang tidak, kadang null dikirim sebagai string kosong, kadang nama kolom database ikut bocor ke response apa adanya. Buat backend developer ini kelihatan sepele, tapi buat tim frontend yang mengonsumsi API itu, ini sumber bug harian. Saya sudah beberapa kali membereskan API lama yang tidak pakai Resource sama sekali, langsung return $model dari controller, dan hasilnya selalu sama: frontend penuh optional chaining dan null check di mana-mana karena tidak ada yang bisa dipercaya dari bentuk response-nya.
Laravel sebenarnya sudah menyediakan alat untuk masalah ini sejak lama, yaitu API Resource. Tapi menurut pengalaman saya, punya Resource class saja tidak otomatis menyelesaikan masalah kalau prinsip di baliknya tidak dipegang. Berikut prinsip yang saya pakai.
Konsistensi field, bukan sekadar mapping
Godaan paling umum saat menulis Resource adalah menulis ulang semua kolom tabel satu-satu, persis seperti nama di database. Ini terasa aman tapi sebenarnya cuma memindahkan masalah SELECT * ke lapisan JSON. Prinsip yang saya pegang: setiap field yang keluar dari Resource harus punya nama yang stabil dan bentuk yang bisa diprediksi, tidak peduli bagaimana skema database berubah di belakang layar.
Contoh sederhana, kolom di database saya is_active bertipe tinyint. Kalau saya kirim apa adanya, frontend bisa menerima 1, 0, atau bahkan "1" tergantung driver database. Saya biasa memaksa tipenya eksplisit di Resource:
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'is_active' => (bool) $this->is_active,
'created_at' => $this->created_at?->toIso8601String(),
];
}
Hal yang sama berlaku untuk field yang mungkin kosong. Saya selalu memilih satu representasi untuk "tidak ada nilai": null, bukan kadang string kosong kadang null kadang field-nya hilang sama sekali dari response. Frontend cuma perlu belajar satu aturan sekali, lalu bisa dipercaya di semua endpoint.
Jangan biarkan struktur database bocor ke response
Ini yang paling sering saya temukan di API yang ditulis buru-buru: nama kolom internal, foreign key mentah, bahkan enum value dari database ikut nongol di response tanpa disaring. Masalahnya bukan cuma estetika. Kalau frontend mulai bergantung pada bentuk mentah tabel, setiap kali saya migrasi ulang tabel atau mengubah nama kolom untuk kebutuhan internal, frontend ikut rusak, padahal secara bisnis tidak ada yang berubah.
Resource harus jadi batas yang tegas antara model bisnis dan kebutuhan tampilan. Contoh, kalau saya punya relasi doctor_id di tabel, response idealnya tidak sekadar mengirim ID mentah itu, tapi objek ringkas yang memang dibutuhkan frontend:
'doctor' => new DoctorSummaryResource($this->whenLoaded('doctor')),
Dengan begini, kalau suatu saat saya menambah tabel perantara atau mengubah cara relasi disimpan, kontrak API ke frontend tidak ikut goyah selama bentuk doctor di response tetap sama. Ini juga alasan kenapa saya menghindari toArray() otomatis dari Eloquent model untuk endpoint publik. Praktis di awal, tapi begitu ada kolom sensitif atau kolom internal baru ditambahkan ke tabel, kolom itu otomatis ikut terekspos tanpa saya sadari.
Prinsip yang sama juga berlaku untuk pesan error dan validasi: jangan sampai nama kolom database atau detail exception mentah ikut terkirim ke client, karena itu juga bentuk kebocoran struktur internal, cuma lewat jalur yang berbeda.
Versioning API tanpa bikin backend jadi rumit sendiri
Cepat atau lambat, kontrak API akan perlu berubah: field baru wajib, field lama dihapus, atau bentuk nested object berubah total. Kesalahan yang paling sering saya lihat adalah mengubah Resource yang sudah dipakai frontend versi lama secara langsung, dengan asumsi "paling cuma tambah field, tidak masalah". Menambah field biasanya memang aman, tapi mengubah tipe field atau menghapus field yang sudah dipakai konsumen lama itu breaking change, walau terasa kecil dari sisi backend.
Untuk kasus ini saya lebih suka strategi versioning yang sederhana: taruh versi di path route (/api/v1/..., /api/v2/...) dan biarkan tiap versi punya Resource class sendiri, misalnya PatientResourceV1 dan PatientResourceV2. Keduanya boleh berbagi query dan logic di controller atau service, yang beda hanya lapisan presentasi. Ini lebih mudah dirawat dibanding versioning lewat header custom, terutama kalau konsumennya bukan cuma satu frontend internal tapi juga integrasi pihak ketiga yang lebih nyaman baca dari URL.
Aturan praktis yang saya pakai: kalau perubahan cuma menambah field baru yang opsional, itu boleh masuk ke versi yang sama. Begitu ada field yang berubah tipe, berubah makna, atau dihapus, itu tandanya perlu versi baru, bukan menimpa versi lama. Konsumen lama tetap jalan tanpa perlu buru-buru migrasi, dan saya sebagai backend developer tidak perlu koordinasi rilis serempak dengan semua tim frontend setiap kali ada perubahan.
Takeaway
- Paksa tipe dan bentuk tiap field di Resource, jangan biarkan tipe kolom database menentukan bentuk response.
- Perlakukan Resource sebagai batas tegas: yang keluar adalah kontrak bisnis, bukan cerminan struktur tabel.
- Kalau perubahan mengubah makna atau tipe field yang sudah dipakai, itu alasan untuk versi baru, bukan menimpa yang lama.
Dengan tiga prinsip ini, API Resource berhenti jadi sekadar boilerplate wajib Laravel, dan mulai berfungsi sebagai kontrak yang bisa dipercaya oleh siapa pun yang mengonsumsi API itu, termasuk diri saya sendiri enam bulan kemudian.