REST memberi kita cara berpikir — resource, verb, representasi — tetapi tidak memberi kita daftar resource-nya. Menentukan resource apa saja yang hidup di API kita adalah keputusan desain pertama dan paling berdampak: dari situlah URL, method, struktur payload, dan bahkan skema database mengikuti. Kesalahan kecil di tahap ini berlipat menjadi kebingungan di setiap endpoint; sebaliknya, resource yang tepat membuat API terasa membaca dirinya sendiri. Pada chapter ini kita melatih mata untuk melihat domain sebagai kumpulan kata benda, lalu menerjemahkannya menjadi URL yang jujur, konsisten, dan bebas dari kata kerja menyamar.

Menggali Resource dari Domain #

Titik awal pemodelan bukanlah database, melainkan cerita domain. Ceritakan bagaimana aplikasi dipakai — “tim membuat proyek, setiap proyek berisi tugas, pengguna menugaskan tugas kepada anggota, dan diskusi menempel pada tugas sebagai komentar” — lalu lingkari semua kata benda. Kata benda yang layak disimpan, diidentifikasi, dan diakses sendirian adalah calon resource; kata kerja dalam cerita itu bukan resource, melainkan aksi yang akan ditampung oleh HTTP verb. Aturan sederhananya: resource adalah kata benda, method adalah kata kerja.

Latihan paling cepat untuk mendeteksi pemodelan yang keliru adalah membaca URL kita keras-keras. /buat-tugas terdengar seperti perintah kepada server — itu kebiasaan RPC yang kita tinggalkan di chapter memahami REST . /tugas terdengar seperti benda — dan perintah lengkapnya hidup di gabungan method dan path:

Kebiasaan kata kerjaRancangan resourceKalimat yang terbentuk
POST /buat-tugasPOST /tugasbuatlah anggota baru pada koleksi tugas
POST /tugas/42/tandai-selesaiPATCH /tugas/42ubah sebagian tugas bernomor 42
GET /daftar-tugas?proyek=7GET /proyek/7/tugasambil koleksi tugas milik proyek 7
POST /hapus-komentar?id=9DELETE /komentar/9hapus komentar bernomor 9

Ketika domain kita kompleks, jangan kaget jika tidak semua kata benda menjadi resource. Laporan rekapitulasi bulanan, hasil pencarian, atau sesi login bisa saja lebih natural dimodelkan sebagai resource hasil (hasil-laporan, hasil-pencarian) ketimbang dipaksakan menjadi tabel database. Kebalikannya juga benar: kata benda yang hanya hidup sebagai properti — “judul tugas” misalnya — tidak perlu jadi resource. Tolok ukurnya selalu sama: apakah benda ini layak punya identitas sendiri dan diakses lewat URL-nya sendiri?

Merancang URL yang Jujur #

Method dan path bekerja sebagai satu kesatuan kosakata — pasangan keduanya adalah “kalimat” API kita. Fungsi kecil berikut adalah kamus rute dalam bentuk paling telanjang: ia menerima pasangan method dan path, lalu mengembalikan operasi domain yang dimaksud. Bentuk inilah yang nanti kita serahkan kepada router sungguhan di chapter routing dan endpoints :

php
<?php

function operasiUntuk(string $method, string $path): string
{
    return match (true) {
        $method === 'GET' && $path === '/tugas' => 'daftar seluruh tugas',
        $method === 'POST' && $path === '/tugas' => 'buat tugas baru',
        $method === 'GET' && preg_match('#^/tugas/\d+$#', $path) === 1 => 'lihat satu tugas',
        $method === 'PATCH' && preg_match('#^/tugas/\d+$#', $path) === 1 => 'ubah sebagian field tugas',
        $method === 'DELETE' && preg_match('#^/tugas/\d+$#', $path) === 1 => 'hapus tugas',
        $method === 'GET' && preg_match('#^/proyek/\d+/tugas$#', $path) === 1 => 'daftar tugas dalam satu proyek',
        default => 'tidak dikenal',
    };
}

foreach ([
    ['GET', '/tugas'],
    ['POST', '/tugas'],
    ['PATCH', '/tugas/42'],
    ['GET', '/proyek/7/tugas'],
    ['POST', '/tugas/42/kirim-ulang'],
] as [$method, $path]) {
    echo str_pad("$method $path", 28), ' => ', operasiUntuk($method, $path), "\n";
}

Output yang Dihasilkan:

text
GET /tugas                   => daftar seluruh tugas
POST /tugas                  => buat tugas baru
PATCH /tugas/42              => ubah sebagian field tugas
GET /proyek/7/tugas          => daftar tugas dalam satu proyek
POST /tugas/42/kirim-ulang   => tidak dikenal

Mari kita bedah cara kerja kamus tersebut:

  1. Dua baris pertama menunjukkan kekuatan koleksi: path yang sama persis /tugas bercabang menjadi dua operasi berbeda hanya karena method berganti — GET membaca koleksi, POST menambahkan anggotanya.
  2. Pola #^/tugas/\d+$# menangkap seluruh anggota koleksi: satu tugas spesifik diidentifikasi segmen terakhir path, dan regex memastikan segmen itu memang angka.
  3. GET /proyek/7/tugas adalah sub-resource: tugas dalam konteks proyek tertentu — pola relasi yang kita bahas tuntas pada bagian khusus di bawah.
  4. Baris terakhir menolak POST /tugas/42/kirim-ulang dengan tidak dikenal — bukan karena regexnya kurang, melainkan karena kirim-ulang adalah kata kerja yang menyamar di path. Desain yang tepat untuknya kita bedah di bagian menghindari kata kerja.

Tunggal atau Jamak #

Koleksi di URL lebih baik berbentuk jamak: /tugas dibaca sebagai kumpulan, dan anggotanya diberi identitas setelah garis miring — /tugas/42. Kebiasaan jamak ini selaras dengan semantik HTTP: GET pada koleksi berarti “ambil semua anggotanya”, POST berarti “tambahkan satu anggota”, dan keduanya hanya masuk akal jika path memang menunjuk kumpulan. URL tunggal seperti /tugas/42 adalah member dari koleksi tersebut — satu-satunya tempat di mana PATCH dan DELETE bermakna.

Untuk penamaan, dunia industri mayoritas memakai kata benda bahasa Inggris (/tasks, /projects, /comments) karena URL kerap dikonsumsi developer lintas negara. Contoh-contoh course ini memakai /tugas dan /proyek agar setara dengan domain berbahasa Indonesia yang kita pakai sejak awal — dan itu sah. Aturan yang tidak bisa ditawar hanyalah konsistensi: campuran /tasks dan /proyek/7/tugas dalam satu API memaksa pemanggil menghafal pengecualian, dan pengecualian adalah pajak atas setiap integrasi baru.

ID, Slug, dan Identitas #

Member resource butuh identitas di path, dan pilihan klasiknya ada dua: ID numerik dari database (/artikel/4217) atau slug — teks pendek yang terbaca manusia (/artikel/menghindari-overengineering). Slug biasanya diturunkan dari judul lewat normalisasi kecil: huruf kecil semua, karakter selain alfanumerik menjadi tanda hubung:

php
<?php

function buatSlug(string $judul): string
{
    $slug = strtolower($judul);
    $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);

    return trim($slug, '-');
}

foreach ([
    'Belajar REST API dengan PHP',
    'Menghindari Overengineering — Refleksi Desain',
    'PATCH vs PUT: Kapan Memakai yang Mana?',
] as $judul) {
    echo buatSlug($judul), "\n";
}

Output yang Dihasilkan:

text
belajar-rest-api-dengan-php
menghindari-overengineering-refleksi-desain
patch-vs-put-kapan-memakai-yang-mana

Dua langkah fungsi tersebut bekerja bertingkat: strtolower() menyeragamkan kapitalisasi, lalu preg_replace() mengganti setiap rentetan karakter non-alfanumerik — spasi, tanda baca, em-dash — dengan satu tanda hubung, dan trim() membersihkan tepinya. Hasilnya stabil, mudah diketik, dan ramah dibaca di hasil pencarian. Kedua gaya identitas punya wilayah kerja masing-masing:

AspekID numerikSlug
Kestabilanpermanen sejak lahirbisa berubah saat judul diedit
Keterbacaantidak bermaknamendeskripsikan isi
Ukuran dan indeksringkas, ideal sebagai kuncilebih panjang, tapi tetap bisa diindeks
Cocok untukdata internal, relasi antar tabelkonten publik yang dibagikan orang

Praktik yang menyatukan keduanya juga umum: /artikel/4217-menghindari-overengineering membawa ID sebagai identitas sejati dan slug sebagai pemanis yang boleh berubah — router cukup membaca segmen numeriknya. Apa pun pilihannya, satu larangan tegas: identitas yang terekspos di URL jangan pernah bocorkan detail teknis yang berhak berubah, seperti ID yang bisa ditebak urutan pembuatannya pada data sensitif, atau nama kolom database.

Relasi sebagai Sub-Resource #

Hubungan antar resource diterjemahkan menjadi sub-resource: tugas milik proyek hidup di GET /proyek/7/tugas, komentar milik tugas di GET /tugas/42/komentar. Kepemilikan terbaca langsung dari struktur path — segmen di depan adalah induk, segmen di belakang adalah anggota miliknya. Struktur bersarang ini ekspresif, tetapi sewajarnya saja: setiap tingkat tambahan memperpanjang URL, memperbanyak kombinasi yang harus dipikirkan router, dan menuntut pemanggil menghafal hierarki penuh.

URLPenilaian
GET /proyek/7/tugastepat — dua tingkat, kepemilikan jelas
GET /tugas/42/komentartepat — komentar milik satu tugas
GET /proyek/7/tugas/42/komentar/9terlalu dalam — identitas sudah lengkap di /komentar/9
GET /tugas?proyek=7padanan sah — relasi jadi filter, bukan path

Panduan praktisnya: berhenti di dua tingkat penumpukan. Lebih dalam dari itu, anggap resource sudah punya identitas global sendiri dan layak diakses langsung — /komentar/9 tidak memerlukan leluhur seluruhnya untuk ditemukan. Alternatif di baris terakhir tabel juga penting dipegang: relasi yang longgar atau opsional lebih natural sebagai query parameter daripada sebagai hierarki path, dan pemanggil yang ingin tugas tersebut di proyek mana pun cukup memanggil GET /tugas/42.

Menghindari Kata Kerja di Path #

Kata kerja di path hampir selalu merupakan tanda bahwa pemodelan resource-nya belum tuntas. Dua penyembuhannya hanya ada dua: perubahan keadaan dikembalikan ke method yang tepat, dan proses yang memang bukan perubahan data dimodelkan ulang sebagai resource hasil. Tabel berikut memetakan yang paling sering muncul:

URL bermasalahRancangan yang tepatAlasannya
POST /tugas/42/tandai-selesaiPATCH /tugas/42 body {"status": "selesai"}perubahan keadaan memang pekerjaan PATCH
POST /tugas/42/kirim-ulangPOST /tugas/42/pengiriman-ulangprosesnya adalah resource baru: notifikasi kirim ulang
GET /cari-tugas?q=phpGET /tugas?q=phppencarian adalah query atas koleksi, bukan resource sendiri
POST /arsipkan-tugas/42PATCH /tugas/42 body {"arsip": true}arsip adalah field keadaan

Perhatikan baris kedua — pengecualian yang jujur. Aksi yang benar-benar menjalankan proses (mengirim ulang notifikasi, meminta reset password, membatalkan pesanan) boleh menjadi POST pada sub-resource kata bendanya: /tugas/42/pengiriman-ulang menciptakan satu resource notifikasi kirim-ulang, dan keberadaannya bisa ditanyakan kembali nanti. Batas antara “keadaan yang berubah” dan “proses yang berjalan” memang pertimbangan desain — pertanyaan penentunya: apakah hasilnya layak disimpan dan ditanya kembali? Jika ya, ia resource; jika tidak, ia cukup menjadi field.

Granularitas PATCH #

Method PUT dan PATCH sama-sama mengubah resource, tetapi dengan janji yang berbeda: PUT mengirim representasi utuh pengganti, sedangkan PATCH hanya mengirim perbedaannya. Untuk tugas dengan delapan field, PUT /tugas/42 memaksa pemanggil menyertakan kedelapan field — field yang lupa ikut terkirim akan terhapus — sementara PATCH cukup membawa yang berubah:

json
{
    "status": "selesai"
}
json
{
    "judul": "Menulis dokumentasi API versi 2",
    "penerima": "[email protected]"
}

Dua body di atas adalah dua PATCH yang sah pada tugas yang sama: yang pertama hanya mengubah status, yang kedua mengubah judul dan penerima sekaligus tanpa menyentuh field lain. Granularitas seperti ini membuat pemanggil hemat — form yang hanya mengubah satu field tidak perlu membaca-lalu-menulis ulang seluruh objek — tetapi juga menuntut kejelasan semantik yang konsisten. Konvensi yang paling umum mengikuti JSON Merge Patch (RFC 7386): field yang muncul di body akan ditimpa, field yang tidak muncul dibiarkan, dan field bernilai null dihapus. Aturan main apa pun yang kita pilih, tulis di dokumentasi dan patuhi di seluruh endpoint — pemanggil tidak seharusnya menebak-nebak apakah null berarti “kosongkan” atau “jangan sentuh”.

Ringkasan #

Resource modeling dimulai dari cerita domain: kata bendanya menjadi resource, kata kerjanya menjadi method, dan relasinya menjadi sub-resource dengan kedalaman yang ditahan di dua tingkat. Identitas member dinyatakan lewat ID numerik atau slug yang stabil, koleksi konsisten bernama jamak, dan perubahan keadaan diteruskan ke PATCH dengan granularitas yang dijanjikan secara konsisten. Dengan daftar resource dan peta URL di tangan, pertanyaan berikutnya sudah menunggu: bagaimana PHP menerima semua pasangan method-path ini dan mengarahkannya ke handler yang tepat — pekerjaan routing, pembahasan chapter berikutnya.

Referensi #