Error handling is a crucial part of any backend application. In Express.js, an error can occur due to various reasons such as invalid user input, database failures, or unexpected issues in the application logic. To ensure a smooth user experience and maintainability, implementing a global error handler is essential.
In this blog, we will explore what a global error handler is, how to implement it in Express.js, and best practices to follow.
π What is Global Error Handling in Express.js?
In Express.js, middleware functions are executed in a sequence, and errors can propagate through them. A global error handler is a special middleware function that:
β
Catches errors from any part of the application.
β
Prevents the app from crashing due to unhandled errors.
β
Provides consistent error responses.
β
Helps in debugging and logging.
By defining a centralized error handler, we can manage all application errors in one place.
π How to Implement a Global Error Handler in Express.js
Step 1: Basic Express App Setup
First, create a basic Express server.
const express = require("express");
const app = express();
// Middleware to parse JSON
app.use(express.json());
// Sample Route
app.get("/", (req, res) => {
throw new Error("Something went wrong!");
});
// Start the server
app.listen(3000, () => console.log("Server running on port 3000"));
At this stage, if an error occurs in a route, Express will return a default HTML error response. We need a custom global error handler to return structured JSON responses.
Step 2: Define a Global Error Handler Middleware
An error-handling middleware must have four parameters: (err, req, res, next)
.
// Global Error Handling Middleware
app.use((err, req, res, next) => {
console.error(err.stack); // Log the error
res.status(500).json({
success: false,
message: err.message || "Internal Server Error",
});
});
πΉ Important: The global error handler must be defined after all routes; otherwise, it wonβt work.
π Throwing and Handling Errors in Express.js
1οΈβ£ Synchronous Errors
Simply throwing an error inside a route will be caught by the error handler:
app.get("/sync-error", (req, res) => {
throw new Error("Synchronous error occurred!");
});
2οΈβ£ Asynchronous Errors
For async functions, always pass the error to next(err)
:
app.get("/async-error", async (req, res, next) => {
try {
await someAsyncOperation();
} catch (err) {
next(err); // Pass the error to the global handler
}
});
π Enhancing the Global Error Handler
Handling Different Error Types
You can customize responses based on error types:
app.use((err, req, res, next) => {
let statusCode = err.status || 500;
let message = err.message || "Internal Server Error";
if (err.name === "ValidationError") {
statusCode = 400;
message = "Invalid data provided";
}
res.status(statusCode).json({
success: false,
error: message,
});
});
Creating a Custom Error Class
To make error handling even cleaner, define a custom error class:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
Now, you can throw errors like this:
app.get("/custom-error", (req, res, next) => {
return next(new AppError("Resource not found", 404));
});
Modify the error handler to handle this class:
app.use((err, req, res, next) => {
res.status(err.statusCode || 500).json({
success: false,
message: err.message,
});
});
β‘ Using express-async-errors
for Cleaner Async Error Handling
Instead of manually wrapping async functions in try-catch, install express-async-errors
:
npm install express-async-errors
Then import it in your server.js
:
require("express-async-errors");
Now, async errors will automatically be passed to the global handler:
app.get("/async-error", async (req, res) => {
const data = await someAsyncOperation(); // No try-catch needed
res.json(data);
});
π¨ Common Mistakes to Avoid
β Defining the Error Handler Before Routes
Error handlers must be defined after all routes.
β Wrong Order:
app.use((err, req, res, next) => { /* Error Handler */ }); // β
app.get("/", (req, res) => { throw new Error("Test Error"); });
β
Correct Order:
app.get("/", (req, res) => { throw new Error("Test Error"); });
app.use((err, req, res, next) => { /* Error Handler */ }); // β
β Not Logging Errors for Debugging
Always log errors for debugging:
console.error(err.stack);
β Not Handling Different Error Types
Handle validation errors separately for better responses.
π― Best Practices for Global Error Handling
β
Keep the error handler at the bottom of the middleware stack.
β
Use a custom error class for cleaner error handling.
β
Log errors to a file or a monitoring service (e.g., Winston, Sentry).
β
Handle validation errors differently from server errors.
β
Use express-async-errors
to avoid repetitive try-catch blocks.
π Conclusion
A well-implemented global error handler in Express.js ensures that your application is robust, maintains a good user experience, and makes debugging easier. By following best practices, handling different error types, and using custom error classes, you can create a clean and maintainable error-handling mechanism.
πΉ Now youβre ready to handle errors like a pro in your Express.js apps! π
Top comments (0)