Error Handling — API

Error handling yang konsisten membuat API mudah digunakan dan di-debug. Prinsip: Gunakan status code yang tepat Format error yang konsisten Pesan yang…

Request yang gagal tetap harus dijawab dengan rapi. Error handling di API artinya tiga hal: status code yang tepat, bentuk body error yang sama di semua endpoint, dan gak ada detail dalaman server yang ikut terkirim. Bayangin papan jadwal di bandara. Penerbangan yang bermasalah selalu ditulis dengan format yang sama: kode penerbangan, status (DELAYED, CANCELLED), dan satu kalimat untuk penumpang. Penumpang gak perlu tahu baut mana yang rusak. Detail kerusakannya dicatat teknisi di buku log, bukan ditempel di papan.

🧪 Tebakan cepat: handler GET /books/1/reviews punya bug, dan error handler-nya menjawab res.status(500).json({ error: err.stack }). Apa yang diterima client: pesan "Something went wrong", stack trace lengkap dengan path file server, atau body kosong? Tebak dulu, jawabannya ada di bagian "Jangan kirim stack trace".

Status code: 4xx salah client, 5xx salah server

Digit pertama status code sudah memberi tahu siapa yang perlu bertindak:

Status Kapan Contoh di Toko Buku
400 Bad Request request-nya rusak, gak bisa dibaca body JSON yang kurung kurawalnya gak ditutup
401 Unauthorized belum dikenali GET /profile tanpa token
403 Forbidden sudah login, tapi gak punya izin Siti (customer) menghapus buku
404 Not Found yang diminta gak ada GET /books/99
409 Conflict bentrok dengan data yang sudah ada menambah buku yang judulnya sudah terdaftar
422 Unprocessable Content terbaca, tapi melanggar aturan harga -5000
500 Internal Server Error bug di server book.reviews ternyata undefined
503 Service Unavailable server sementara gak bisa melayani database sedang mati

User yang sudah login tapi gak berhak atas sebuah resource dijawab 403, bukan 401.

Satu bentuk error untuk semua endpoint

Kalau tiap route menulis error-nya sendiri ({"error": "..."} di sini, {"message": "..."} di sana, {"err": true} di tempat lain), frontend harus menebak bentuknya satu per satu. Pilih satu bentuk, pakai di mana-mana. Dua bentuk yang umum, untuk kesalahan yang sama:

Bentuk sederhana, yang dipakai latihan:

{ "error": { "code": "NOT_FOUND", "message": "Book not found" } }

Problem Details (RFC 9457), standar resmi untuk error di HTTP API, dengan header Content-Type: application/problem+json:

{
  "type": "https://api.tokobuku.test/problems/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Book 99 does not exist.",
  "instance": "/books/99"
}

Dua-duanya baik. Yang penting: satu bentuk untuk semua endpoint.

Error handler terpusat

Di Express, route cukup melempar error-nya. Satu middleware khusus di paling akhir yang menjawab semuanya:

class HttpError extends Error {
  constructor(status, code, message) {
    super(message);
    this.status = status;
    this.code = code;
  }
}

app.get("/books/:id", (req, res) => {
  const book = books.find((b) => b.id === Number(req.params.id));
  if (!book) throw new HttpError(404, "NOT_FOUND", "Book not found");
  res.json({ data: book });
});

// Paling akhir, sesudah semua route:
app.use((err, req, res, next) => {
  const status = err.status || 500;
  const code = err.code || "INTERNAL_ERROR";
  const message = status === 500 ? "Something went wrong" : err.message;
  if (status === 500) console.error(err); // detail lengkap cuma masuk log server
  res.status(status).json({ error: { code, message } });
});
Request Yang terjadi Response
GET /books/1 bukunya ada 200 {"data": {...}}
GET /books/99 throw new HttpError(404, ...) 404 {"error": {"code": "NOT_FOUND", "message": "Book not found"}}
GET /books/1/reviews TypeError, bug 500 {"error": {"code": "INTERNAL_ERROR", "message": "Something went wrong"}}

Jangan kirim stack trace

Stack trace adalah catatan di mana error terjadi: nama error, file, dan nomor baris. Dengan res.status(500).json({ error: err.stack }), client menerima kira-kira ini:

TypeError: Cannot read properties of undefined (reading 'map')
    at /home/deploy/tokobuku-api/src/routes/reviews.js:14:31
    at Layer.handle [as handle_request] (/home/deploy/tokobuku-api/node_modules/express/lib/router/layer.js:95:5)

Itu jawaban tebakannya: stack trace lengkap ikut terkirim. Path folder server, nama file, nomor baris, dan library yang dipakai jadi peta untuk penyerang, sementara pengguna gak bisa berbuat apa-apa dengannya. Client cukup dapat INTERNAL_ERROR dan "Something went wrong". Detailnya masuk log server lewat console.error(err), tempat developer mencarinya.

⚠️ Jebakan yang sering ketemu

TL;DR: 4xx berarti error di sisi client (perbaiki request-nya), 5xx berarti error di sisi server. 400 request rusak, 401 belum dikenali, 403 sudah login tapi gak punya izin, 404 gak ada, 409 bentrok, 422 melanggar aturan, 500 bug. Pakai satu bentuk error di semua endpoint: {"error": {"code": "NOT_FOUND", "message": "..."}}, atau Problem Details RFC 9457 (type, title, status, detail, instance, dengan Content-Type: application/problem+json). Route melempar error (throw atau next(err)), dan satu error handler dengan empat parameter (err, req, res, next), dipasang paling akhir lewat app.use, membaca err.status dan menjawab semuanya. Error tak terduga jadi 500 INTERNAL_ERROR "Something went wrong"; stack trace cuma masuk log, gak pernah ke client.

Yang akan kamu pelajari