DEV Community

Shyam Tala
Shyam Tala

Posted on

Global Error Handling in Express.js: Best Practices

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"));
Enter fullscreen mode Exit fullscreen mode

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",
  });
});
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή 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!");
});
Enter fullscreen mode Exit fullscreen mode

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
  }
});
Enter fullscreen mode Exit fullscreen mode

πŸš€ 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,
  });
});
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, you can throw errors like this:

app.get("/custom-error", (req, res, next) => {
  return next(new AppError("Resource not found", 404));
});
Enter fullscreen mode Exit fullscreen mode

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,
  });
});
Enter fullscreen mode Exit fullscreen mode

⚑ 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
Enter fullscreen mode Exit fullscreen mode

Then import it in your server.js:

require("express-async-errors");
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

🚨 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"); });
Enter fullscreen mode Exit fullscreen mode

βœ… Correct Order:

app.get("/", (req, res) => { throw new Error("Test Error"); });
app.use((err, req, res, next) => { /* Error Handler */ }); // βœ…
Enter fullscreen mode Exit fullscreen mode

❌ Not Logging Errors for Debugging

Always log errors for debugging:

console.error(err.stack);
Enter fullscreen mode Exit fullscreen mode

❌ 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)