Error Handling
Catching synchronous and asynchronous errors gracefully using a global error handler.
🧑🏫 Sabse pehle — simple mein samjho#
Agar reataurant mein khana banate waqt aag lag jaye (Error), toh chef chup chaap baitha nahi rehta. Wo manager ko batata hai, aur manager customer ko politely sorry bolta hai. Code mein bhi yahi hona chahiye. Agar database fat jaye, toh server crash nahi hona chahiye. Humein ek Global Error Handler banana chahiye jo saari galtiyon ko ek jagah pakad kar client ko ek saaf "500 Internal Server Error" message de.
1. Synchronous Error Handling#
Express automatically catches synchronous errors and passes them down the chain to your error handler.
app.get('/sync-error', (req, res) => {
throw new Error("This is a sync error!");
// Express catches this automatically
});
2. Asynchronous Error Handling (The tricky part)#
Before Express 5.0, if an asynchronous operation (like fetching from a database) threw an error, Express could not catch it automatically. Your server would crash. You had to explicitly pass the error to next().
// Express 4.x - The painful way
app.get('/async-bad', async (req, res, next) => {
try {
const user = await Database.findUser(); // If this fails...
res.json(user);
} catch (err) {
next(err); // ...you MUST explicitly hand it to next()
}
});
The Solution: express-async-errors#
To avoid writing try-catch blocks in every single route, backend developers use a package called express-async-errors. You just require it once at the top of your app.js, and it automatically patches Express to catch async errors.
require('express-async-errors'); // Magic!
// Now you can write clean async routes without try-catch!
app.get('/async-good', async (req, res) => {
const user = await Database.findUser(); // If this fails, it auto-forwards to the global handler
res.json(user);
});
3. The Global Error Handling Middleware#
To catch all these errors (both sync and async passed via next), you define a special middleware at the very bottom of your file, after all other routes.
An error-handling middleware is unique: it takes exactly 4 arguments (err, req, res, next).
// 404 Handler (Placed just before the global error handler)
app.use((req, res, next) => {
res.status(404).json({ message: "Route not found!" });
});
// The Global Error Handler (Must be the last app.use)
app.use((err, req, res, next) => {
console.error(err.stack); // Log it for the developer
// Check if a specific status code was set, otherwise default to 500
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
success: false,
message: err.message || "Something went terribly wrong!",
// Only send the raw stack trace if in development mode!
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
});
By structuring your app this way, you ensure that no matter where an error occurs, your application will never crash ungracefully, and the client will always receive a formatted JSON response.