Express menangani error dengan middleware khusus yang menerima empat argumen, (err, req, res, next), dan didaftarkan setelah semua route-mu. Express mengirim ke sana setiap error yang dilempar di dalam route, setiap error yang kamu berikan ke next(err), dan di Express 5, setiap promise yang reject dari handler async. Handler itu menentukan status code dan mengirim satu response error, jadi sisa kodemu cukup melempar error.
GET /users/99 menjawab 404 dengan {"error":"No user with id 99"}, dan Console menampilkan baris dari error handler. Kirim GET /users/1 dan route-nya menjawab seperti biasa; error handler hanya berjalan saat ada yang salah.
Error yang ditangkap Express untukmu
Ada tiga cara mencapai error handler dari sebuah route, dan di Express 5 ketiganya bekerja sama:
GET /async menjawab 500 dengan {"error":"Rejected in an async route"}; GET /sync dan GET /next berakhir di handler yang sama dengan pesannya masing-masing. Sekarang hapus next dari parameter error handler lalu kirim GET /sync lagi: dengan tiga parameter, Express menganggapnya middleware biasa, melewatinya, dan menjawab dengan halaman error HTML default-nya.
Error async: Express 5 vs Express 4
Route /async bekerja karena Express 5 menangkap promise yang reject. Express 4 tidak: error-nya menjadi unhandled rejection dan request-nya menggantung.
| Express 4 | Express 5 | |
|---|---|---|
throw di handler sync | Ditangkap | Ditangkap |
next(err) | Ditangkap | Ditangkap |
Promise yang reject dari handler async | Tidak ditangkap, request menggantung | Ditangkap |
Error di callback setTimeout atau event | Tidak ditangkap, proses crash | Tidak ditangkap, proses crash |
Di Express 4, bungkus setiap handler async atau install package express-async-errors:
// Express 4 only
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.findUser(req.params.id);
res.json(user);
}));
Baris terakhir sama di kedua versi: Express tidak bisa melihat error yang dilempar di callback yang tidak dipanggilnya. Ubah API callback menjadi promise (sebagian besar Node punya versi promise, seperti fs/promises) lalu await hasilnya.
Handler 404 dan error JSON yang konsisten
API lebih mudah dipakai saat setiap error punya bentuk yang sama. Beri error sebuah status, ubah route yang tidak cocok menjadi error 404, dan biarkan satu handler memformat semuanya:
GET /nope tidak cocok dengan route mana pun, jadi handler 404 mengubahnya menjadi error dan jawabannya {"error":"Cannot GET /nope","status":404}. Kirim GET /users/99 dan error yang dilempar route mendapat bentuk yang sama. Kirim POST /users dengan body {"name": }: express.json() gagal mem-parse-nya dan memanggil next() dengan error yang status-nya 400, jadi jawabannya {"error":"Unexpected token '}', \"{\"name\": }\" is not valid JSON","status":400}.
Jangan bocorkan stack trace
Pesan error dari HttpError milikmu ditulis untuk client. Pesan dan stack dari semua yang lain (driver database, salah ketik yang melempar TypeError) bisa membocorkan path file, query, dan versi library. Handler di atas mengirim Internal server error untuk setiap 5xx dan mencatat error aslinya di server. if (res.headersSent) return next(err); menangani error yang terjadi setelah sebagian response sudah terkirim: satu-satunya langkah aman saat itu adalah membiarkan Express menutup koneksinya.
Handler default milik Express mengikuti NODE_ENV: ia menyertakan stack trace di halaman HTML-nya kecuali NODE_ENV bernilai production. Atur nilai itu di server production-mu (NODE_ENV=production node server.js), tetapi jangan bergantung padanya: handler buatan sendiri yang tidak pernah mengirim err.stack lebih aman.
Kesalahan umum
Mendaftarkan error handler sebelum route. Express hanya meneruskan error ke depan, jadi handler yang ada di atas sebuah route tidak pernah melihat error dari route itu. Taruh paling akhir, setelah handler 404.
Menangkap error lalu tidak melakukan apa-apa. try/catch yang hanya mencatat log membuat request menggantung. Di dalam catch, panggil next(err), lempar lagi, atau kirim response.
Mengirim dua response. Melempar error setelah res.json() membuat error handler mencoba mengirim lagi. Cek res.headersSent, seperti di contoh.
Untuk cara kerja middleware biasa dan next(), lihat middleware Express.
Pertanyaan yang sering diajukan
Bisakah aku punya lebih dari satu error handler di Express?
Bisa. Semuanya berjalan berurutan, dan masing-masing mengirim response atau memanggil next(err) untuk meneruskan error-nya. Pembagian yang umum adalah satu handler untuk mencatat log dan satu untuk mengirim response.
Apa yang terjadi kalau error handler melempar error?
Express meneruskan error baru itu ke error handler berikutnya, dan kalau tidak ada, ke handler bawaannya, yang mengirim halaman 500. Buat error handler tetap sederhana supaya tidak bisa gagal sendiri.
Status code apa yang sebaiknya dipakai untuk response error?
Pakai 4xx saat client mengirim sesuatu yang salah (400 input salah, 401 belum login, 403 tidak diizinkan, 404 tidak ditemukan, 409 konflik) dan 500 saat kodemu yang gagal. Jangan pernah menjawab 200 dengan error di body.
Bagaimana mengembalikan error validasi untuk beberapa field?
Jawab 400 dengan sebuah daftar, misalnya { "errors": [{ "field": "email", "message": "is required" }] }. Dengan zod, petakan result.error.issues ke bentuk itu di error handler supaya setiap route melaporkan input yang salah dengan cara yang sama.