Tiga jam saya habiskan untuk satu endpoint milik mitra. Responsnya 200 OK, jadi kode saya menganggap semuanya beres. Ternyata di dalam body ada {"status": "error", "message": "product not found"}. Setelah saya perbaiki pemeriksaan itu, muncul masalah kedua: setiap kali koneksi putus dan saya mengirim ulang request yang sama, jumlah order di sisi mereka bertambah. Dua retry, dua order.
Tidak ada satu pun aturan REST yang dilanggar di API itu. Endpoint-nya jalan, JSON-nya valid, dokumentasinya ada. Yang bermasalah adalah hal yang lebih sulit ditulis di dokumen: perilakunya tidak bisa ditebak.
Delapan pola di bawah ini semuanya soal itu. Bukan soal apakah API-mu "RESTful", tapi apakah orang lain bisa memakainya tanpa menebak-nebak.
1. URL menamai benda, method menamai pekerjaan
Kesalahan yang paling sering saya temui adalah endpoint bergaya fungsi: /getUsers, /createOrder, /deleteProduct. Awalnya terasa jelas. Masalahnya, URL jadi menjelaskan aksi, padahal HTTP sudah punya alat untuk itu.
Pisahkan perannya. URL menyebut benda, method menyebut apa yang kamu lakukan padanya.
GET /orders daftar order
POST /orders buat order baru
GET /orders/{id} ambil satu order
PUT /orders/{id} ganti seluruh isi order
DELETE /orders/{id} hapus order
Pola ini juga lebih tahan umur. Satu resource bisa melayani banyak operasi tanpa kamu mengarang URL baru setiap kali ada kebutuhan.
2. Satu konvensi penamaan, dipakai sampai ujung
Ambil contoh: satu bagian API memakai /user, bagian lain /customers, bagian ketiga /customer-profiles. Masing-masing jalan sendiri, tapi sekarang setiap orang yang integrasi harus menghafal aturanmu.
Pilih satu gaya untuk resource yang namanya lebih dari satu kata, lalu pakai di semua tempat. customer-profiles atau customerProfiles atau customer_profiles, pilih satu. Yang mana pun tidak masalah, yang penting tidak campur.
Konsistensi ini bukan soal kerapian. Efeknya langsung: orang bisa menebak endpoint yang belum pernah dia buka.
3. Method dipakai sesuai artinya, bukan sesuai yang bikin jalan
Butuh update user, dan di satu tempat kamu memakai POST, di tempat lain PUT, di tempat lain lagi PATCH. Ketiganya "berhasil". Tapi klien sekarang harus mempelajari aturan buatanmu, bukan mempercayai HTTP.
| Method | Artinya | Berulang aman? |
|---|---|---|
| GET | ambil data, tanpa efek samping | ya |
| POST | buat resource baru atau picu proses | tidak |
| PUT | ganti seluruh representasi resource | ya |
| PATCH | ubah sebagian field | ya, kalau dirancang begitu |
| DELETE | hapus resource | ya |
Perbedaan PATCH dan PUT paling sering dikaburkan:
PATCH /users/42
{ "email": "[email protected]" }
PUT /users/42
{ "name": "Abd. Asis", "email": "[email protected]", "role": "admin" }
Yang pertama mengubah satu field dan membiarkan sisanya. Yang kedua mengganti seluruh isi record, jadi field yang tidak kamu kirim akan hilang atau kembali ke nilai default.
Di balik semua ini ada idempotensi: mengirim GET, PUT, atau DELETE lima kali hasilnya harus sama dengan sekali. POST sengaja melanggar aturan itu, dan di situlah muncul order dobel saat retry. Jaringan putus, klien mengulang, timeout terjadi. Pilih method berdasarkan artinya, bukan berdasarkan mana yang kebetulan bikin endpoint jalan.
4. Status code jangan bohong
Contoh yang benar-benar menyakiti integrator: HTTP 200, tapi isi body bilang operasinya gagal karena produk tidak ditemukan. Klien sekarang harus memeriksa dua tempat untuk tahu apa yang sebenarnya terjadi.
Biarkan status code membawa hasil sebenarnya. 2xx untuk berhasil, 4xx kalau permintaan klien yang salah, 5xx kalau servermu yang salah.
| Kode | Kapan dipakai |
|---|---|
| 200 / 201 | berhasil, 201 saat resource baru dibuat |
| 204 | berhasil, tidak ada isi untuk dikembalikan |
| 400 | payload tidak valid secara bentuk |
| 401 / 403 | belum login / sudah login tapi tidak berhak |
| 404 | resource tidak ada |
| 409 | konflik keadaan, misalnya data duplikat |
| 422 | validasi gagal pada isi permintaan |
| 429 | kena batas laju |
| 500 | kesalahan di sisi server |
Kamu tidak perlu menghafal semuanya. Yang perlu: status code yang benar-benar mewakili hasilnya, dan tidak mengarang protokol error sendiri di atas HTTP.
5. Bentuk error seragam
Status code memberi tahu kategori kegagalan. Klien biasanya butuh lebih dari itu. Bandingkan dua respons ini:
{ "error": "Something went wrong" }
{
"code": "PRODUCT_NOT_FOUND",
"message": "Tidak ada produk dengan ID tersebut",
"status": 404
}
Yang kedua jauh lebih berguna: aplikasi bisa bereaksi pada code, manusia bisa membaca message, dan yang men-debug punya pegangan. Untuk kegagalan validasi, sebut field mana yang salah, bukan hanya "permintaan tidak valid":
{
"code": "VALIDATION_FAILED",
"message": "Ada 2 field yang perlu diperbaiki",
"status": 422,
"errors": {
"email": ["Format email tidak valid"],
"qty": ["Harus bilangan bulat minimal 1"]
}
}
Formatnya bebas kamu tentukan. Yang wajib adalah bentuknya sama di semua endpoint, supaya klien bisa menulis satu penangan error untuk seluruh API.
6. Filter masuk query, bukan ke dalam path
Mulai dari /products. Lalu tambah kategori di path. Lalu status stok. Lalu rentang harga. Lalu pencarian. Lalu urutan. Setahun kemudian URL-nya sudah tidak bisa dibaca manusia.
Kriteria opsional tempatnya di query string:
GET /products?category=sepatu&inStock=true&sort=price&page=2
Path menyebut benda, query mempersempit hasil. Satu hal yang perlu dihindari: jangan pakai query untuk menyamar sebagai aksi. GET /products?action=delete membuat method berbohong tentang apa yang sedang terjadi, dan setiap alat di jalan (cache, proxy, crawler) akan salah mengira request itu aman diulang.
7. Perubahan API dilakukan dengan sadar
API tidak pernah beku. Respons yang sempurna hari ini bisa perlu berubah enam bulan lagi. Yang berbahaya adalah mengubahnya tanpa memikirkan klien yang sudah bergantung padamu.
Contoh nyata: dulu price berupa angka, sekarang kamu ingin price jadi objek berisi nilai dan mata uang. Itu bukan "cuma menambah field". Strukturnya berubah, dan klien lama akan rusak.
Sebelum buru-buru membuat versi baru, tanya dulu apakah memang perlu.
| Perubahan | Aman? |
|---|---|
| Menambah field opsional di respons | biasanya aman |
| Menambah parameter query opsional | biasanya aman |
| Mengubah tipe data sebuah field | tidak |
| Menghapus atau mengganti nama field | tidak |
| Mengubah arti status code | tidak |
| Mengubah struktur paginasi | tidak |
Kalau memang perlu versi baru, pilih satu mekanisme dan pakai konsisten: versi di URL, versi di header, terserah. Yang penting ada jalur migrasi, bukan pengumuman mendadak bahwa klien harus berubah minggu ini.
8. Format request dan respons konsisten
Satu endpoint mengembalikan created_at, endpoint lain createdAt, endpoint ketiga date_created. Semuanya JSON yang sah. Tapi sekarang setiap klien harus mengingat mana yang berlaku di mana.
Masalah yang sama muncul di format tanggal, bentuk paginasi, struktur error, dan penamaan properti. Jadi tetapkan sekali, lalu pegang:
- satu gaya JSON untuk seluruh API
- satu konvensi penamaan properti
- satu bentuk respons error
- satu pola paginasi dan filter
- satu format tanggal, sebutkan zona waktunya
Tujuannya bukan membuat buku aturan setebal dokumen kontrak. Tujuannya supaya endpoint berikutnya terasa familier. Setelah orang memahami satu bagian API-mu, dia seharusnya bisa menebak sisanya dengan benar.
Kalau kamu bekerja di Laravel, pembungkusan respons lewat API Resource membantu menjaga bentuk ini tetap seragam tanpa mengulang kode di setiap controller. Sisi lain yang sering terlewat adalah pengujian: perubahan kecil di resource bisa mengubah kontrak tanpa ada yang sadar, dan itu yang saya tulis di mendeteksi schema drift pada API PHP.
Yang sebenarnya dikejar bukan gelar "RESTful"
Banyak developer mengukur API dari seberapa patuh ia pada teori REST. Itu berguna, tapi bukan intinya. API tidak jadi baik hanya karena GET, POST, PUT, dan DELETE-nya dipakai dengan benar.
API yang baik adalah API yang bisa dipakai orang tanpa harus membuka dokumentasi setiap sepuluh menit. Nama resource masuk akal. Method berperilaku seperti dugaan. Status code berarti sesuatu. Error punya bentuk yang tetap. Filter bekerja sama di semua tempat. Dan perubahan tidak diam-diam merusak orang yang sudah bergantung padamu.
Jadi sebelum menulis endpoint pertama, jawab dulu pertanyaan besarnya: bagaimana resource dinamai, bagaimana koleksi diperlakukan, bagaimana filter berperilaku, method mana yang benar-benar dipakai, bagaimana error terlihat, bagaimana status code diperlakukan, dan bagaimana API ini akan berevolusi.
Begitu jawabannya jelas, menulis endpoint jadi bagian yang mudah. Sisanya cuma disiplin untuk tidak menyimpang di endpoint kesepuluh, ketika tenggat sudah dekat dan jalan pintas terlihat menggoda.







