Praktik Terbaik Penanganan Kesalahan API REST
Respons kesalahan API adalah bagian dari kontraknya. Klien menguraikannya, logika percobaan ulang bercabang berdasarkan respons tersebut, dan tim dukungan menggunakannya saat mendiagnosis masalah. Namun banyak tim hanya merancang alur sukses secara rinci, lalu membiarkan kesalahan mengikuti perilaku default framework. Hasilnya: beberapa format kesalahan dalam satu API, respons 200 dengan "success": false, atau jejak tumpukan yang membocorkan skema basis data.
Panduan ini membahas cara memilih kode status HTTP, membakukan badan kesalahan dengan RFC 9457 Problem Details, memisahkan kode mesin dari pesan manusia, menandai kesalahan yang dapat dicoba ulang, menjaga kerahasiaan respons, serta menguji setiap jalur kegagalan menggunakan Apidog.
Mulai dari kode status, bukan badan respons
HTTP sudah menyediakan lapisan pertama semantik kesalahan. RFC 9110 membagi kode status menjadi:
- 4xx: kesalahan berada pada permintaan klien; mengulang permintaan yang sama biasanya akan gagal.
- 5xx: server atau dependensinya gagal; permintaan klien mungkin valid.
Klien umum, proksi, cache, dan pustaka percobaan ulang sering membuat keputusan berdasarkan kode status tanpa membaca JSON. Karena itu, pilih status dengan konsisten. Gunakan referensi kode status HTTP MDN saat merancang API.
| Situasi | Gunakan | Bukan | Alasan |
|---|---|---|---|
| JSON rusak, tipe konten salah, atau bidang wajib hilang | 400 Bad Request |
422 |
Server tidak dapat mengurai atau memahami permintaan |
| Sintaks valid, tetapi nilai melanggar aturan domain | 422 Unprocessable Content |
400 |
Permintaan dapat diurai, tetapi nilainya tidak valid |
| Kredensial tidak ada atau token kedaluwarsa | 401 Unauthorized |
403 |
Klien belum membuktikan identitas; kirim WWW-Authenticate
|
| Identitas valid, tetapi tidak memiliki izin | 403 Forbidden |
401 |
Otentikasi ulang tidak akan membantu |
| Sumber daya tidak ada atau keberadaannya tidak boleh dikonfirmasi | 404 Not Found |
410 |
Default yang aman dan mencegah pemeriksaan tidak sah |
| Sumber daya sengaja dihapus secara permanen | 410 Gone |
404 |
Klien dan perayap dapat menghapus referensinya |
| Kunci duplikat, versi lama, atau konflik pengeditan | 409 Conflict |
400 |
Permintaan valid, tetapi bertentangan dengan status saat ini |
| Klien melampaui batas kecepatan | 429 Too Many Requests |
503 |
Sertakan Retry-After
|
| Pengecualian tidak tertangani di kode Anda | 500 Internal Server Error |
502 |
Server Anda mengalami kegagalan |
| Layanan upstream mengembalikan data tidak valid ke gateway | 502 Bad Gateway |
500 |
Kegagalan berada di sisi upstream |
| Server kelebihan beban atau sedang dipelihara | 503 Service Unavailable |
500 |
Kesalahan ini bersifat sementara |
| Layanan upstream mengalami timeout | 504 Gateway Timeout |
500 |
Membedakan dependensi lambat dari kode yang rusak |
Dua hal sering tertukar:
-
401vs403adalah keputusan keamanan. Mengembalikan403kepada pemanggil yang belum terautentikasi dapat membocorkan bahwa sumber daya tersebut ada. -
429harus disertaiRetry-After. Tanpa petunjuk penundaan, klien dapat mencoba ulang dalam perulangan ketat. Lihat panduan pembatasan kecepatan API untuk algoritme dan perhitungan header.
Gunakan satu format: RFC 9457 Problem Details
Setelah kode status ditentukan, semua kesalahan API sebaiknya menggunakan satu tipe media dan satu skema. Standarnya adalah RFC 9457 Problem Details, yang disajikan sebagai:
Content-Type: application/problem+json
RFC 9457 mendefinisikan lima anggota inti:
-
type: URI yang mengidentifikasi kategori kesalahan -
title: ringkasan singkat yang mudah dibaca -
status: kode HTTP -
detail: penjelasan kesalahan pada kejadian tersebut -
instance: URI untuk kegagalan spesifik
Anggota lain dapat ditambahkan sebagai ekstensi. Penjelasan RFC 9457 membahas spesifikasi dan perbedaannya dari RFC 7807.
Contoh kegagalan validasi pada endpoint pembayaran:
POST /v1/payments HTTP/1.1
Content-Type: application/json
{ "amount": -1400, "currency": "USD", "source": "card_8xKt2" }
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-error",
"title": "Validasi permintaan gagal",
"status": 422,
"detail": "Satu atau lebih bidang gagal validasi.",
"instance": "/v1/payments/requests/req_9f3c1a7b",
"code": "PAYMENT_VALIDATION_FAILED",
"errors": [
{
"field": "amount",
"code": "AMOUNT_NOT_POSITIVE",
"message": "jumlah harus berupa bilangan bulat positif dalam satuan kecil"
}
],
"request_id": "req_9f3c1a7b"
}
errors[] adalah anggota ekstensi dan sangat berguna bagi klien. Frontend dapat memetakan setiap kesalahan ke bidang formulir yang tepat, bukan menampilkan satu pesan umum.
Pilih format jalur bidang yang stabil—misalnya JSON Pointer atau jalur bertitik—dan gunakan format tersebut di semua endpoint.
Terapkan format yang sama untuk kesalahan yang dihasilkan framework, proxy, dan gateway. Jika handler Anda mengembalikan Problem Details, tetapi load balancer mengembalikan HTML untuk 502, klien tetap harus memelihara dua parser.
Pisahkan kode mesin dan pesan manusia
Contoh di atas memiliki code dan message karena keduanya melayani audiens berbeda.
Kode yang dapat dibaca mesin
Kode seperti berikut adalah bagian dari kontrak:
AMOUNT_NOT_POSITIVE
CURRENCY_UNSUPPORTED
IDEMPOTENCY_KEY_REUSED
Kode harus stabil, terdokumentasi, dan dapat digunakan untuk percabangan program. Jangan meminta klien mengurai prosa:
if (message.includes("positive")) {
// rapuh: perubahan teks dapat mematahkan perilaku klien
}
Pesan untuk manusia
Pesan ditujukan bagi pengembang yang membaca log atau respons. Pesan boleh diperbaiki dan dilokalkan tanpa mengubah perilaku klien.
Gunakan pesan yang menjelaskan masalah dan cara memperbaikinya:
jumlah harus berupa bilangan bulat positif dalam satuan kecil
lebih berguna daripada:
jumlah tidak valid
Jika API mendukung beberapa bahasa, lokalkan message, tetapi pertahankan code. Pemisahan ini juga penting bagi konsumen berbasis LLM. Kesalahan terstruktur membantu agen AI menentukan langkah pemulihan; lihat desain kesalahan API untuk agen AI.
Jangan bocorkan informasi sensitif
Respons kesalahan adalah saluran pengintaian yang menarik bagi penyerang. Middleware kesalahan harus memastikan hal-hal berikut tidak pernah dikirim kepada klien:
- Jejak tumpukan, nama kelas, atau jalur file
- SQL mentah, fragmen kueri, atau kesalahan ORM
- Nama host internal, alamat IP, port, atau nama layanan
- Versi pustaka dan banner framework
- Rahasia, token, atau string koneksi
- Keberadaan akun pengguna pada alur login dan reset kata sandi
Gunakan pola berikut:
- Tangkap semua pengecualian di batas aplikasi.
- Catat pengecualian lengkap di server.
- Sertakan ID permintaan dalam log.
- Kembalikan Problem Details generik kepada ```
Log server menyimpan detail sebenarnya, sedangkan tim dukungan dapat menghubungkan respons dengan log melalui request_id.
Tandai kesalahan yang dapat dicoba ulang
Setiap respons kesalahan harus menjawab pertanyaan: haruskah klien mencoba lagi?
Secara umum:
-
429,502,503, dan504: dapat dicoba ulang dengan exponential backoff dan jitter. -
500: ambigu, tetapi biasanya layak dicoba ulang satu kali secara hati-hati. - Sebagian besar
4xxlain: terminal; jangan ulangi permintaan yang sama.
Timeout perlu perhatian khusus. Permintaan mungkin berhasil setelah klien berhenti menunggu. Endpoint yang mengubah data harus mendukung kunci idempoten agar percobaan ulang tidak menagih atau membuat data dua kali.
Anda juga dapat membuat keputusan ini eksplisit melalui anggota ekstensi:
json
{
"type": "https://api.example.com/problems/rate-limited",
"title": "Terlalu banyak permintaan",
"status": 429,
"code": "RATE_LIMITED",
"retryable": true,
"retry_after_seconds": 30
}
retryable memungkinkan server mengganti default status. Misalnya, subkode tertentu pada 500 dapat ditandai terminal jika percobaan ulang berisiko merusak status.
Dokumentasikan perilaku ini sekali agar semua SDK klien menerapkan strategi percobaan ulang yang seragam.
Kelola korelasi dan versi kontrak
Selalu gunakan ID permintaan
Terima header X-Request-Id dari klien atau buat ID baru. Tulis ID tersebut di setiap baris log dan kembalikan sebagai request_id pada setiap respons kesalahan.
Dalam sistem terdistribusi, sebarkan juga traceparent W3C agar permintaan dapat dilacak di seluruh layanan.
Perlakukan kontrak kesalahan seperti API
Penambahan anggota ekstensi atau kode kesalahan baru biasanya aman. Perubahan berikut adalah breaking change:
- Mengganti nama
errors[].field - Mengubah arti kode yang sudah ada
- Mengubah format Problem Details menjadi format ad hoc
- Menghapus anggota yang sebelumnya digunakan klien
Pertahankan URI type lama untuk semantik lama. Gunakan URI baru untuk semantik baru, dan dokumentasikan bahwa anggota atau kode yang tidak dikenal harus diabaikan.
Aturan kompatibilitas ke depan ini memungkinkan kontrak berkembang tanpa harus membuat versi API baru.
Uji setiap jalur kesalahan di Apidog
Kontrak kesalahan cepat membusuk jika tidak diuji. Alur sukses biasanya diuji dalam demo, sedangkan cabang 422 atau 503 baru terlihat saat pelanggan menemukannya.
Gunakan Apidog untuk menjadikan kegagalan sebagai bagian dari pengujian.
1. Buat skenario pengujian server
Untuk setiap endpoint, tambahkan kasus seperti:
- Kredensial hilang →
401 - Peran tidak memadai →
403 - Jumlah negatif →
422denganerrors[0].codebernilaiAMOUNT_NOT_POSITIVE - Lalu lintas berlebih →
429dengan headerRetry-After
Gunakan asersi untuk memeriksa:
- Kode status
- Header
- Tipe media
- Struktur badan
- Kesesuaian dengan JSON Schema Problem Details
Dengan begitu, perubahan kontrak menyebabkan kegagalan CI, bukan insiden produksi. Lihat Panduan asersi API untuk contoh pola asersi.
2. Gunakan server tiruan untuk klien
Frontend dan SDK perlu menguji respons 4xx dan 5xx sebelum backend dapat menghasilkan kondisi tersebut secara konsisten.
Server tiruan Apidog dapat mengembalikan badan Problem Details dari spesifikasi API, sehingga Anda dapat mensimulasikan:
-
503denganRetry-After: 120 -
409saat pengiriman ganda - Payload validasi lengkap dengan
errors[]
Kemudian amati bagaimana klien menampilkan pesan, memperbaiki input, dan menjalankan percobaan ulang—tanpa stub Express manual atau perubahan sementara pada kode backend.
Rancang kontrak kesalahan, implementasikan sebagai skenario dan mock, lalu jalankan semuanya di CI. Anda dapat mengunduh Apidog dan mengimpor spesifikasi OpenAPI untuk mulai membuat mock dalam beberapa menit.
FAQ
Haruskah menggunakan 400 atau 422 untuk validasi?
Gunakan 400 ketika permintaan salah format dan server tidak dapat memahaminya, seperti JSON tidak valid, tipe konten salah, atau bidang wajib hilang.
Gunakan 422 ketika permintaan berhasil diurai, tetapi nilainya melanggar aturan domain, seperti jumlah pembayaran negatif atau mata uang yang tidak didukung.
Yang terpenting adalah konsistensi di semua endpoint.
Apa itu application/problem+json?
application/problem+json adalah tipe media RFC 9457 untuk Problem Details, yaitu format kesalahan JSON standar bagi API HTTP.
Respons ini membawa type, title, status, detail, dan instance, serta ekstensi seperti errors[]. Tipe media yang terdaftar memungkinkan klien umum dan middleware mengenali kesalahan tanpa konfigurasi khusus.
Kesalahan HTTP mana yang boleh dicoba ulang otomatis?
Coba ulang 429, 502, 503, dan 504 dengan exponential backoff dan jitter. Hormati Retry-After jika tersedia.
Anggap 500 layak untuk satu percobaan ulang yang hati-hati. Jangan mencoba ulang respons 4xx lainnya dengan permintaan identik.
Untuk endpoint yang mengubah data, gunakan kunci idempoten agar percobaan ulang tidak menghasilkan tagihan atau data ganda.
Bagaimana menguji respons kesalahan tanpa merusak backend?
Gunakan simulasi. Arahkan klien ke server tiruan Apidog yang mengembalikan badan 4xx dan 5xx sesuai spesifikasi API, lalu verifikasi perilaku rendering dan percobaan ulang.
Di sisi server, buat skenario untuk payload tidak valid, autentikasi yang hilang, konflik, dan lalu lintas berlebih. Lakukan asersi pada kode status, header, serta skema badan kesalahan, lalu jalankan kedua rangkaian pengujian di CI.
Top comments (0)