Buka *codebase* mana pun yang lebih lama dari dua tahun dan Anda akan menemukan bekas lukanya: /getUser, /user_list, /Users/fetchAll, tiga skema paginasi yang berbeda, dan bidang customerID yang berada di samping order_id dalam respons yang sama. Tidak ada yang merusak apa pun. Semuanya memperlambat semua orang.
Penamaan adalah keputusan desain API termurah yang pernah Anda buat dan yang paling mahal untuk dibatalkan. Setelah klien bergantung pada /getOrders, Anda terpaksa mendukungnya selama bertahun-tahun. Panduan ini memberi Anda aturan konkret untuk setiap keputusan penamaan yang dipaksakan oleh REST API, dengan contoh dan anti-contoh untuk masing-masing. Ini mengikuti pemikiran yang sama dengan panduan REST API kami yang lebih luas untuk pengembang, tetapi memperbesar bagian yang paling sering diperdebatkan oleh tim: apa yang harus disebut sesuatu.
Jika Anda lebih suka menerapkan aturan ini dengan *tooling* daripada dengan komentar *code review*, Apidog memungkinkan Anda menentukan setiap *endpoint* secara visual terhadap skema bersama sebelum ada yang menulis kode. Lebih lanjut tentang itu di akhir.
Gunakan kata benda jamak untuk koleksi
URL menamai sebuah sumber daya, bukan sebuah operasi. Koleksi adalah kumpulan hal-hal, jadi namai mereka sebagai kata benda jamak.
Lakukan:
GET /v1/products
GET /v1/products/89
GET /v1/orders
Jangan:
GET /v1/getProducts
GET /v1/product
GET /v1/productList
Bentuk jamak berfungsi di kedua tingkatan. /products dibaca sebagai "kumpulan produk" dan /products/89 dibaca sebagai "produk 89 dalam koleksi". Penamaan tunggal memaksa URL yang canggung seperti /product/89 untuk satu item tetapi /product untuk banyak, yang dibaca salah. Pedoman API REST Microsoft memutuskan kata benda jamak karena alasan ini, dan sebagian besar API publik (Stripe, GitHub, Shopify) mengikuti jalur yang sama.
Satu pengecualian: sumber daya *singleton*. Jika seorang pengguna hanya memiliki satu keranjang, /users/42/cart tidak masalah. Jangan jamakkan sesuatu dengan kardinalitas satu.
Hindari kata kerja dalam jalur
Metode HTTP adalah kata kerja. Menempatkan kata kerja lain di jalur menduplikasi informasi dan merusak model sumber daya.
Lakukan:
GET /v1/orders/42 (membacanya)
DELETE /v1/orders/42 (menghapusnya)
PATCH /v1/orders/42 (memperbaruinya)
Jangan:
GET /v1/fetchOrder/42
POST /v1/deleteOrder/42
POST /v1/updateOrderStatus
Jalur berbasis kata kerja juga memperbanyak area permukaan Anda. Satu sumber daya dengan empat metode menjadi empat *endpoint* yang harus didokumentasikan, diuji, dan di-*cache* secara terpisah. Pembatalan *cache* juga semakin buruk: CDN dapat meng-*cache* GET /v1/orders/42 dan membatalkannya pada DELETE /v1/orders/42 karena keduanya menunjuk ke URL yang sama. Ini tidak dapat menghubungkan /fetchOrder/42 ke /deleteOrder/42.
Gunakan kebab-case dalam jalur URL
Segmen jalur multi-kata membutuhkan pemisah, dan tanda hubung adalah yang tepat.
Lakukan:
/v1/gift-cards
/v1/shipping-addresses
Jangan:
/v1/giftCards
/v1/gift_cards
/v1/GiftCards
Tiga alasan. Google memperlakukan tanda hubung sebagai pemisah kata untuk pengindeksan, sehingga dokumentasi API publik memiliki peringkat yang lebih baik dengan *kebab-case*. Garis bawah menghilang ketika URL digarisbawahi dalam email atau dokumen. Dan *camelCase* dalam URL mengundang *bug* sensitivitas huruf besar/kecil: /giftCards dan /giftcards adalah URL yang berbeda di sebagian besar server, dan seseorang akan mengetik yang salah. Pedoman API REST Zalando menjadikan *kebab-case* sebagai aturan WAJIB, dan mereka telah menjalankan panduan ini di ratusan layanan internal.
Pilih satu casing JSON dan catat
Untuk nama *field* di dalam *body* permintaan dan respons, jawaban jujurnya adalah: *camelCase* dan *snake_case* sama-sama berfungsi. Yang tidak berfungsi adalah mencampurkannya.
Lakukan (salah satu, secara konsisten):
{ "orderId": 42, "createdAt": "2026-08-30T09:15:00Z", "totalAmount": 4999 }
{ "order_id": 42, "created_at": "2026-08-30T09:15:00Z", "total_amount": 4999 }
Jangan:
{ "orderId": 42, "created_at": "2026-08-30T09:15:00Z", "TotalAmount": 4999 }
*camelCase* memetakan dengan rapi ke klien JavaScript dan Java. *snake_case* lebih mudah dipindai dan cocok dengan nama kolom Ruby, Python, dan sebagian besar SQL; Stripe menggunakannya di mana-mana. Pilih berdasarkan siapa yang paling banyak mengonsumsi API Anda, lalu masukkan pilihan tersebut ke dalam panduan gaya Anda sehingga perdebatan terjadi sekali saja daripada di setiap *pull request*. Pencampuran *casing* adalah inkonsistensi paling umum dalam API dunia nyata karena tim yang berbeda mengirimkan *endpoint* yang berbeda. Itu adalah kegagalan *governance*, bukan kegagalan selera.
Batasi *nesting* hingga dua level
*Nesting* mengekspresikan kepemilikan: /users/42/orders berarti "pesanan milik pengguna 42." Itu berguna. Melebihi dua level, itu berhenti berguna.
Lakukan:
GET /v1/users/42/orders
GET /v1/orders/1337/refunds
Jangan:
GET /v1/users/42/orders/1337/refunds/7/status
*Nesting* yang dalam memaksa klien untuk membawa setiap ID leluhur untuk mencapai sumber daya daun, bahkan ketika daun memiliki ID unik globalnya sendiri. Jika pengembalian dana memiliki ID 7, paparkan di /refunds/7 atau /orders/1337/refunds/7 dan berhenti di situ. Sebuah *smell test* yang baik: jika URL berisi tiga atau lebih ID, ratakanlah. Setelah sebuah pesanan ada, ia tidak membutuhkan penggunanya di jalur; /orders/1337 berdiri sendiri.
Tempatkan pemfilteran, pengurutan, dan paginasi dalam parameter kueri
Jalur mengidentifikasi sumber daya. Parameter kueri memodifikasi cara Anda melihatnya. Jangan pernah menyandikan filter ke dalam jalur.
Lakukan:
GET /v1/orders?status=active&sort=-created_at&limit=50&cursor=eyJpZCI6NDJ9
GET /v1/products?category=electronics&min_price=1000
Jangan:
GET /v1/orders/active
GET /v1/orders/sorted-by-date-desc
GET /v1/getOrdersByStatusAndDate
Pola sort=-created_at (*prefix* minus untuk urutan menurun) berasal dari spesifikasi JSON:API dan menghemat Anda parameter order=desc kedua. Jalur filter seperti /orders/active terlihat tidak berbahaya sampai Anda perlu menggabungkan filter, dan kemudian Anda membuat *endpoint* baru per kombinasi. Nama parameter paginasi layak mendapatkan disiplin yang sama: pilih limit/cursor atau page/per_page sekali dan gunakan kembali pada setiap koleksi. Panduan paginasi API kami membahas *trade-off* *cursor-versus-offset* secara mendalam; aturan penamaan di sini hanyalah untuk seragam mengenai hal itu.
Versi dalam jalur
Anda memiliki dua pilihan utama: segmen jalur (/v1/products) atau *header* (Accept: application/vnd.myapi.v1+json). Versi *header* lebih "murni" REST, karena URL tetap menamai sumber daya yang sama di seluruh versi, dan panduan desain API Google mencatat bahwa kedua pendekatan ada di lapangan. Tetapi versi jalur menang dalam hal operasional: ini terlihat di setiap baris *log*, dapat diuji dari peramban, dapat di-*cache* tanpa *gymnastics* Vary, dan tidak mungkin dilupakan oleh klien. Setiap pengembang yang telah *debug* masalah "berfungsi di *curl*, gagal di *prod*" yang disebabkan oleh *header* versi yang hilang tahu biaya dari alternatifnya. Gunakan /v1/ hanya dengan versi mayor, tanpa /v1.2/; perubahan minor harus bersifat aditif dan tidak *breaking*. Untuk *decision tree* lengkap, termasuk negosiasi konten, lihat perbandingan strategi *versioning* API kami.
Perlakukan ID sumber daya sebagai *opaque*, dan jangan sembarangan membocorkan bilangan bulat berurutan
/orders/41, /orders/42, /orders/43: ID bilangan bulat berurutan memberi tahu siapa pun yang melihat persis berapa banyak pesanan yang Anda proses, dan itu mengundang serangan enumerasi di mana penyerang menjelajahi ruang ID mencari celah otorisasi. Kelas *bug* ini, otorisasi tingkat objek yang rusak, berada di peringkat nomor satu dalam OWASP API Security Top 10.
Lakukan:
GET /v1/orders/ord_9f8e2a71b3
GET /v1/users/550e8400-e29b-41d4-a716-446655440000
Jangan (ketika enumerasi penting):
GET /v1/orders/42
GET /v1/invoices/10883
ID acak yang diawali seperti ord_9f8e2a71b3 milik Stripe adalah pola terkuat: tidak dapat ditebak, deskriptif sendiri dalam *log*, dan aman untuk diekspos. Pemeriksaan otorisasi tetap wajib bagaimanapun caranya. ID *opaque* mengurangi dampak dari pemeriksaan yang hilang; mereka tidak menggantinya. Secara internal Anda dapat menyimpan *primary key* bilangan bulat; aturannya adalah tentang apa yang Anda paparkan di URL.
Model tindakan non-CRUD sebagai sumber daya pengontrol
Cepat atau lambat Anda akan membutuhkan tindakan tanpa pemetaan CRUD yang jelas: membatalkan pesanan, mencoba kembali pembayaran, mengirim ulang email. Jangan menyalurkannya melalui PATCH pada bidang status, dan jangan meletakkan kata kerja di tingkat atas.
Lakukan:
POST /v1/orders/42/cancel
POST /v1/payments/pay_88a1/retry
Jangan:
PATCH /v1/orders/42 { "status": "cancelled" }
POST /v1/cancelOrder { "orderId": 42 }
Ini adalah pola pengontrol, dan ini adalah satu-satunya pengecualian yang diizinkan untuk aturan tanpa kata kerja: kata kerja berada di akhir jalur, terlingkup di bawah sumber daya yang ditindakinya. Pendekatan PATCH terlihat RESTful tetapi menyembunyikan mesin status di dalam pembaruan bidang. Membatalkan pesanan memicu pengembalian dana, melepaskan inventaris, dan mengirimkan notifikasi; berpura-pura itu adalah penulisan bidang memaksa server Anda untuk membandingkan *payload* untuk mendeteksi maksud. Sebuah *endpoint* /cancel menyatakan maksud, memberikan tindakan izin dan jejak auditnya sendiri, dan menyisakan ruang untuk *input* spesifik tindakan seperti alasan pembatalan.
Jaga konsistensi *casing* untuk *header* dan parameter kueri
Dua permukaan yang lebih kecil, disiplin yang sama. *Header* kustom menggunakan *Hyphenated-Pascal-Case*, sesuai konvensi HTTP: Idempotency-Key, Request-Id. Lewati *prefix* X- yang lama; itu sudah tidak digunakan lagi oleh RFC 6648 pada tahun 2012. Nama *header* tidak peka huruf besar/kecil di *wire*, tetapi dokumen dan SDK Anda tetap harus menuliskannya dengan satu cara.
Parameter kueri harus cocok dengan *casing* *body* JSON Anda. Jika *body* Anda menggunakan *snake_case*, tulis ?min_price=1000&created_after=2026-01-01, bukan ?minPrice=1000. Seorang pengembang yang membaca created_at dalam respons dan harus mengetik createdAfter dalam kueri akan salah pada percobaan pertama, begitu juga semua orang setelah mereka.
Kumpulan aturan lengkap sekilas
| # | Aturan | Lakukan | Jangan |
|---|---|---|---|
| 1 | Kata benda jamak untuk koleksi | /products, /products/89 |
/getProducts, /productList |
| 2 | Tidak ada kata kerja dalam jalur | DELETE /orders/42 |
POST /deleteOrder/42 |
| 3 | Segmen jalur *kebab-case* | /gift-cards |
/giftCards, /gift_cards |
| 4 | Satu *casing* JSON, didokumentasikan | order_id di mana-mana |
orderId dan order_id dicampur |
| 5 | Maksimal dua level *nesting* | /orders/1337/refunds |
/users/42/orders/1337/refunds/7 |
| 6 | Filter dan paginasi dalam parameter kueri | ?status=active&sort=-created_at |
/orders/active |
| 7 | Versi mayor dalam jalur | /v1/products |
/v1.2/products, *header* versi |
| 8 | ID sumber daya *opaque* | /orders/ord_9f8e2a71b3 |
/orders/42 (publik, dapat dihitung) |
| 9 | Pola pengontrol untuk tindakan | POST /orders/42/cancel |
PATCH dengan {"status":"cancelled"} |
| 10 | Konsisten *casing* *header* dan parameter | Idempotency-Key, ?min_price= |
X-IDEMPOTENCY_KEY, ?minPrice= dicampur |
Menerapkan konvensi dalam skala besar
Panduan gaya dalam sebuah wiki tidak mengubah apa pun. Tim yang API-nya tetap konsisten memiliki satu kebiasaan: mereka merancang terlebih dahulu dan menerapkan konvensi sebelum kode ada, yang merupakan inti dari tata kelola API dalam praktik.
Di sinilah Apidog mendapatkan tempatnya dalam alur kerja. *Endpoint* didefinisikan dalam desainer visual *schema-first*, sehingga jalur, *casing*, dan nama parameter adalah artefak desain eksplisit daripada *string* yang terkubur dalam kode pengontrol. Komponen bersama berarti skema Pagination, Error, dan Money didefinisikan sekali dan digunakan kembali di setiap *endpoint*; tidak ada yang menciptakan kembali per_page sebagai pageSize pada layanan baru. Dan karena desain hidup di ruang kerja tim dengan tinjauan bawaan, seorang pemimpin dapat menangkap /getUserOrders pada waktu desain, ketika penggantian nama membutuhkan satu klik, daripada setelah tiga klien terintegrasi dengannya. Spesifikasi kemudian menggerakkan dokumen, server *mock*, dan pengujian, sehingga nama yang Anda setujui adalah nama yang dikirimkan semua orang. Unduh Apidog dan coba gratis dengan *endpoint* baru Anda berikutnya; memodifikasi API lama itu sulit, tetapi mempertahankan garis pada yang baru tidak sulit.
FAQ
Haruskah URL REST berbentuk jamak atau tunggal?
Jamak, untuk setiap sumber daya dengan lebih dari satu instans: /products, /orders, /users. Bentuk jamak tetap alami baik untuk koleksi (/orders) maupun satu anggota (/orders/42). Cadangkan nama tunggal untuk *singleton* sejati seperti /users/42/cart. Jika Anda menginginkan penalaran yang lebih mendalam di balik pemodelan sumber daya, panduan kami tentang apa itu REST API membahasnya dari prinsip-prinsip dasar.
Apakah camelCase atau snake_case lebih baik untuk nama field JSON?
Tidak ada yang menang berdasarkan keunggulan. *camelCase* cocok untuk konsumen yang banyak menggunakan JavaScript; *snake_case* lebih mudah dibaca dan cocok dengan API publik Python, Ruby, dan Stripe. Aturan yang kuat: pilih satu, tuliskan ke dalam panduan gaya Anda, dan terapkan dalam tinjauan skema. Pencampuran *casing* di seluruh *endpoint* lebih merugikan daripada salah satu pilihan.
Haruskah saya menempatkan versi API di URL atau di header?
Gunakan jalur (/v1/orders) kecuali Anda memiliki persyaratan *hypermedia* yang kuat. Versi jalur muncul di *log*, *cache*, dan pengujian *browser* tanpa upaya klien. *Versioning header* menjaga URL tetap stabil di seluruh versi tetapi gagal secara diam-diam ketika klien melupakan *header*. Hanya versi mayor; kirimkan perubahan minor sebagai pembaruan aditif, non-breaking.
Apakah kata kerja pernah dapat diterima dalam jalur API REST?
Ya, di satu tempat: *endpoint* pengontrol untuk tindakan non-CRUD, seperti POST /orders/42/cancel atau POST /payments/pay_88a1/retry. Kata kerja berada di akhir jalur, terlingkup di bawah sumber dayanya, dan metode selalu POST. Di tempat lain, metode HTTP membawa kata kerja dan jalur hanya berisi kata benda.
