Error handling Middleware

Error handling is a critical aspect of web application development. In Express.js, error-handling middleware plays a vital role in catching and responding to errors that occur during the request-response cycle. This chapter will cover everything you need to know about error-handling middleware, from basic concepts to advanced techniques, with detailed explanations and practical examples.

What is Error-Handling Middleware?

Error-handling middleware is a function that takes four arguments: err, req, res, and next. It is used to handle errors that occur during the execution of route handlers and other middleware. In Express.js, error-handling middleware functions are defined after all other middleware and route handlers.

Basic Error-Handling Middleware

The simplest form of error-handling middleware catches errors and responds with a generic message.

				
					const express = require('express');
const app = express();

app.get('/', (req, res) => {
    throw new Error('Something went wrong!');
});

// Basic error-handling middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • An error is thrown in the route handler.
  • The error-handling middleware catches the error, logs the stack trace, and sends a 500 status with a message.

Output: Navigating to http://localhost:3000/ results in a 500 status response with “Something broke!”.

Defining Custom Error-Handling Middleware

Custom error-handling middleware can provide more detailed error messages and responses.

				
					const express = require('express');
const app = express();

app.get('/', (req, res) => {
    throw new Error('Something went wrong!');
});

// Custom error-handling middleware
app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).json({ error: err.message });
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • The error message is logged, and the response includes a JSON object with the error message.

Output: Navigating to http://localhost:3000/ results in a 500 status response with a JSON object: { "error": "Something went wrong!" }.

Catching Synchronous and Asynchronous Errors

Express.js can handle both synchronous and asynchronous errors with error-handling middleware.

				
					const express = require('express');
const app = express();

app.get('/sync', (req, res) => {
    throw new Error('Synchronous error!');
});

app.get('/async', async (req, res, next) => {
    try {
        throw new Error('Asynchronous error!');
    } catch (err) {
        next(err);
    }
});

// Error-handling middleware
app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).json({ error: err.message });
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • Synchronous errors are thrown directly.
  • Asynchronous errors are caught and passed to the next function.

Output: Navigating to /sync results in a 500 status response with a JSON object: { "error": "Synchronous error!" }. Navigating to /async results in a 500 status response with a JSON object: { "error": "Asynchronous error!" }.

Using the next Function for Error Handling

The next function can be used to pass errors to the error-handling middleware.

				
					const express = require('express');
const app = express();

app.get('/', (req, res, next) => {
    const err = new Error('Something went wrong!');
    next(err);
});

// Error-handling middleware
app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).json({ error: err.message });
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • The next function is used to pass the error to the error-handling middleware.

Output: Navigating to http://localhost:3000/ results in a 500 status response with a JSON object: { "error": "Something went wrong!" }.

Centralized Error Handling

Centralized error handling involves creating a single error-handling middleware function that handles all errors in the application.

				
					const express = require('express');
const app = express();

// Routes
app.get('/', (req, res) => {
    throw new Error('Something went wrong!');
});

app.get('/user', (req, res) => {
    res.send('User route');
});

// Centralized error-handling middleware
app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).json({ error: err.message });
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • A centralized error-handling middleware function is defined to handle all errors.

Output: Any error in the application will result in a 500 status response with a JSON object containing the error message.

Error-Handling Best Practices

  • Always Catch Errors: Ensure all possible errors are caught, especially in asynchronous code.

  • Provide Meaningful Error Messages: Return user-friendly error messages while logging detailed information for developers.

  • Use Centralized Error Handling: Centralize your error-handling logic to maintain consistency and reduce redundancy.

  • Avoid Leaking Sensitive Information: Be cautious not to expose sensitive information in error messages.

  • Test Error Handling: Regularly test your error-handling logic to ensure it works as expected.

Advanced Error Handling

Handling Different Types of Errors

Different types of errors can be handled differently based on their nature.

				
					const express = require('express');
const app = express();

app.get('/', (req, res) => {
    throw new Error('Something went wrong!');
});

app.get('/not-found', (req, res, next) => {
    const err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// Advanced error-handling middleware
app.use((err, req, res, next) => {
    if (err.status === 404) {
        res.status(404).json({ error: err.message });
    } else {
        console.error(err.stack);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • The error-handling middleware checks the error status and handles 404 errors differently from other errors.

Output: Navigating to /not-found results in a 404 status response with { "error": "Not Found" }. Navigating to / results in a 500 status response with { "error": "Internal Server Error" }.

Creating Custom Error Classes

Creating custom error classes allows for more fine-grained error handling.

				
					const express = require('express');
const app = express();

class NotFoundError extends Error {
    constructor(message) {
        super(message);
        this.status = 404;
    }
}

app.get('/not-found', (req, res, next) => {
    next(new NotFoundError('This resource was not found'));
});

// Error-handling middleware
app.use((err, req, res, next) => {
    if (err instanceof NotFoundError) {
        res.status(err.status).json({ error: err.message });
    } else {
        console.error(err.stack);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • A custom error class NotFoundError is created.
  • The error-handling middleware checks if the error is an instance of NotFoundError and handles it accordingly.

Output: Navigating to /not-found results in a 404 status response with { "error": "This resource was not found" }.

Real-World Example: Building a Robust Error-Handling System

Let’s build a more comprehensive example that includes:

  • Validation errors.
  • Authentication errors.
  • Server errors.
				
					const express = require('express');
const app = express();
const bodyParser = require('body-parser');

// Middleware
app.use(bodyParser.json());

// Custom error classes
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.status = 400;
    }
}

class AuthenticationError extends Error {
    constructor(message) {
        super(message);
        this.status = 401;
    }
}

// Routes
app.post('/login', (req, res, next) => {
    const { username, password } = req.body;
    if (!username || !password) {
        return next(new ValidationError('Username and password are required'));
    }
    if (username !== 'admin' || password !== 'password') {
        return next(new AuthenticationError('Invalid credentials'));
    }
    res.send({ token: 'mysecrettoken' });
});

app.get('/protected', (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (!authHeader || authHeader !== 'Bearer mysecrettoken') {
        return next(new AuthenticationError('Unauthorized access'));
    }
    res.send('Protected resource');
});

// Error-handling middleware
app.use((err, req, res, next) => {
    if (err instanceof ValidationError) {
        res.status(err.status).json({ error: err.message });
    } else if (err instanceof AuthenticationError) {
        res.status(err.status).json({ error: err.message });
    } else {
        console.error(err.stack);
        res.status(500).json({ error: 'Internal Server Error' });
    }
});

// Start the server
app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

				
			

Explanation:

  • Custom error classes ValidationError and AuthenticationError are created.
  • Routes check for validation and authentication errors and use next to pass errors to the error-handling middleware.
  • The error-handling middleware differentiates between validation, authentication, and server errors.

Output:

  • POST /login with missing or incorrect credentials results in a 400 or 401 status response with an appropriate error message.
  • GET /protected without proper authorization results in a 401 status response with “Unauthorized access”.

Error handling in Express.js is essential for building robust and maintainable applications. By using error-handling middleware, you can catch and respond to errors effectively, ensuring a smooth user experience and easier debugging. This chapter covered basic and advanced error-handling techniques, providing a comprehensive guide to implementing error handling in your Express.js applications. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India