DEV Community

Cover image for Error Handling in Express.js/Node.js - Part One: Synchronous Code
Prateek Shukla
Prateek Shukla

Posted on

Error Handling in Express.js/Node.js - Part One: Synchronous Code

One of the most crucial phases of software development, error management frequently goes unnoticed, especially by novice programmers. So let's dive deep into the common practices of Error Handling while working with Express.js/Node.js.

Every developer thinks of writing the perfect code for their application but can still end up finding errors when that application goes online. Now there can be numerous reasons for these errors. Some might be from the User's end and others from the Server's end. Following are a few reasons, why the user might be seeing an error when using your application -:

  1. Your own API or some third-party API being used by your application might be down or
  2. Server timeouts or slow loading times can occur at your server's end or
  3. If your Application is unable to connect to the Database or
  4. An User input validation can cause an error. Etc.

Since Express.js is the most popular Node.js framework for server-side JavaScript, you wouldn't be expecting it to leave out some Error Handling by itself. So yes, Express.js comes with a built-in Error Handler known as the Default Error Handler. This Default Error Handler uses the Error class of JavaScript to handle errors in the application. When an error occurs in an Express application, it is often thrown as an instance of the Error class or one of its sub-classes. The default error handler then catches these errors and handles them by sending an error response to the client in the form of Error Messages, Stack Trace, and HTTP Status codes.

Image description

Before we dive deeper into throwing and handling our custom errors in Express.js let's get some terminologies out of the way-:

1. Middleware -: Express Middleware or Express Hook is the function that runs during the Request-Response life cycle. They have access to the request object (req) and the response object (res). Everything in Express is some sort of middleware e.g. app.get(), app.put(), app.use(), etc are all middleware. Multiple middlewares/hooks can be chained together (or can be written one after the other) by using next().
2. next() -: In Express.js, next() is a function that is used in middleware functions to call the next middleware function in the chain. When a middleware function is complete, it can either respond to the client or call next() to pass control to the next middleware function. If next() is not called, the request will hang and the client will eventually time out.
3. next(err) -: The next function takes an optional argument, which is an error object. If an error object is passed to next(), Express.js will proceed to the next error-handling Middleware function that matches the error. If there are no matching error-handling Middleware functions, the Default Error Handler will be used. If the error object is not passed to the next() then the next available Route Middleware is called.

Custom Error Handling in Express.js/Node.js -:

1. Using "throw" with the Default Error Handler -: We can use the throw keyword to throw an instance of the "Error" class of JavaScript with a custom message as follows -:

array = [1,2,3,4,5,6,7,8,9,10]
app.get('/values/:id', (req, res) => {
    const foundValue = array.find(element => element === parseInt(req.params.id));
    if(!foundValue)
        throw new Error("Value doesn't exist")
    res.send('HOME PAGE!')
})
Enter fullscreen mode Exit fullscreen mode

For the above code when we try to find the value, 11 in the array, we get the following output -:

Image description

Since we did not define our own custom error handler in our code, when the throw new Error("Value not found") line was executed the Built-in Handler was called and the above HTML response was displayed on the browser window.

Note-: If you would notice carefully , next(err) and throw err seems to be doing the same job and you are absolutely right but there's a minute difference between how they are executed.
 
throw new AppError('message', 404) is used to immediately throw an error, which means that the rest of the code block will not be executed, and the error will be handled by the error handling middleware registered on the app. On the other hand, next(new AppError('message', 404)) is used to pass an error to the next middleware function in the stack. This means that the next middleware function will be executed, and after it's execution the control would jump back to the original middleware.
Here's a code example to demonstrate the difference-:

Case 1 -:

app.get('/admin', (req, res, next) => {
    next(new Error('You are not an Admin!')) 
    console.log("1")
})

app.use((err, req, res, next) => {
    const { status = 500, message = 'Something Went Wrong' } = err;
    console.log("2")
    res.status(status).send(message)
    console.log("3")
})
Enter fullscreen mode Exit fullscreen mode

Output-:

2
3
1
Enter fullscreen mode Exit fullscreen mode

Case 2-:

app.get('/admin', (req, res, next) => {
    throw Error('You are not an Admin!')
    console.log("1") //unreachable code
})

app.use((err, req, res, next) => {
    const { status = 500, message = 'Something Went Wrong' } = err;
    console.log("2")
    res.status(status).send(message)
    console.log("3")
})
Enter fullscreen mode Exit fullscreen mode

Output-:

2
3
Enter fullscreen mode Exit fullscreen mode

2. Defining our own Custom Error Handlers -: According to Express.js doc, we define error-handling middleware functions in the same way as other middleware functions, except error-handling functions have four arguments instead of three: (err, req, res, next). For the same code to find a value in the array we can define our custom error handler (Condition-: to be written as the last middleware in our code) as follows -:

app.use((err, req, res, next) => {
     err.message = err.message + `<br>%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        <br>%%%%%%%%%%%%%%%%%ERROR%%%%%%%%%%%%%%%%%%%
        <br>%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        `
     res.status(404).send(err.message)
 })
Enter fullscreen mode Exit fullscreen mode

In the above code, we have manipulated the value of err.message, which is a built-in property of the Error object, by adding our custom message to it. By using res.status(404) we are telling Express to send the response with the 404 status code. Here is the output for the above code -:

Image description
In the above code if we still want to call the Default Error Handler after the execution of our Custom Error Handler we can do the following -:

app.use((err, req, res, next) => {
     err.message = err.message + `<br>%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        <br>%%%%%%%%%%%%%%%%%ERROR%%%%%%%%%%%%%%%%%%%
        <br>%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        `
     next(err)
 })
Enter fullscreen mode Exit fullscreen mode

Output -:

Image description
3. Defining a Custom Error Class -: Creating a separate js file for error handling is recommended so as to standardize the error response format throughout the application and also to separate the error handling logic from the actual application. Custom error classes can be extended to provide additional functionality or customization. For example, you could create a custom error class that logs errors to a database or sends an email to the developer. Here's a basic Custom Error Class-:
AppError.js -:

class AppError extends Error {
    constructor(message, status) {
        super();
        this.message = message;
        this.status = status;
    }
}

module.exports = AppError;
Enter fullscreen mode Exit fullscreen mode

In the above code we are extending JavaScript's Error class so that when we throw an instance of AppError, it can be identified as an error by the Error Handling Middlewares.
Index.js -:

const AppError = require('./AppError.js')

const verifyPassword = (req, res, next) => {
    const { password } = req.query;
    if (password === process.env.PASSWORD) {
        next();
    }
    throw new AppError('Wrong Password. Please Try again
', 401);
}
app.get('/secret', verifyPassword, (req, res) => {
    res.send('THE SECRET IS: I am Iron Man')
})
app.use((err, req, res, next) => {
    const { status = 500, message = 'Something Went Wrong' } = err;
    res.status(status).send(message)
})
Enter fullscreen mode Exit fullscreen mode

In the above code, this time our app.get() has two callback functions, the first is verifyPassword() and the second is (req, res). Whenever someone will try to access the /secret route express.js will first run the verifyPassword() which will verify the password and will throw an instance of AppError in case it doesn't match with the actual password and then, since we are calling next() in the function body, the second callback function will be called. Using a wrong password the output would be as follows -:

Image description
Above are some of the most popular ways of Error Handling in Synchronous code in Express.js/Node.js. The world revolves a little differently for the Asynchronous side of the coin, so hold tight for Part 2.

Top comments (1)

Collapse
 
dimensioncloud profile image
DimensionCloud

Hi, can u revised my code ?