Course Banner

Robust Error Handling

Introduction

In the previous activity, you applied basic Express error handing and applied it to handling the "File not found" error that is so common. In this activity, you'll write and implement a "Higher-Order" function which will be applied to all other processes in the application where an error can occur (which is pretty much everywhere). As given in the previous activity, this same link is repeated to help you better understand Higher-Order functions. I encourage you to read Higher Order Functions in JavaScript - Beginner's Guide to learn more about these special functions.

Video Overview

The video provides a general overview of the activity, but does not contain the detail needed to complete each process. Watch the video to obtain a general idea, but follow the written steps to complete the activity. This is the Transcript of the video.

Write the Function

  1. Find and open the utilities > index.js file.
  2. Scroll to the bottom of the last function, but before the module.exports command and add some blank lines.
  3. Add the following function in this blank space:
    /* ****************************************
     * Middleware For Handling Errors
     * Wrap other function in this for 
     * General Error Handling
     **************************************** */
    Util.handleErrors = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next)
  4. Ensure there are no warnings or errors.
  5. Save the file.
  6. An Explanation

    • Lines 1-5 - a multi-line comment explaining the function.
    • Line 6 - the entire function, with the individual parts explained below:
    • Util.handleErrors = - declares the property which is appended to the "Util" object.
    • fn => (req, res, next) => an arrow function named "fn" which accepts request, response, and next as parameters along with another arrow function.
    • Promise.resolve(fn(req, res, next)) a "wrapper" that accepts a function as a parameter of the "Promise.resolve" function. In other words, the wrapped function is called and attempts to fulfill its normal process, but now does so within a JavaScript promise. If it succeeds, then the promise is resolved and everything continues normally.
    • .catch(next) - if there is an error, then the Promise "fails", the error that caused the failure is "caught" and forwarded to the next process in the application chain.
    • Because it is an "error" that is being passed via the "next" function, the Express Error Handler will catch the error and then build and deliver the error view to the client.

Middleware

The function you just wrote is middleware. Middleware is applied between the request arriving at the server, and the response sending something back to the client. This is almost always done in a route. The concept is relatively straight forward - middleware is a way to handle a sequence of interactions and move from one to the next as needed. It is "middleware" because it occurs between receiving the request and before sending the response. Here is a link to an 11-minute video that I think does a pretty good job of explaining and illustrating middleware - https://youtu.be/MIr1oxQ3pao. We will begin using middleware, a lot, in the rest of the course.

Time to Test

The most basic route in the application is the one that delivers the application home view. That's were we will test our code.

  1. Open the server.js file.
  2. Check the require statements at the top of the file and ensure that the utilities > index.js file is required. The path may not include "index.js" because it is a default name. Instead, the statement may look like this: "const utilities = require("./utilities/")".
  3. If the require statement is missing, be sure to add it.
  4. Scroll down to the "Routes" section and find the route to deliver the home or index view.
  5. Wrap the call to the baseController in the "handleErrors" function, like this:
    utilities.handleErrors(baseController.buildHome)
  6. The call to the "buildHome" function in the "baseController" is being sent into the higher order function you just wrote in the utilities file. Once again, it attempts to run its normal process. If successful, the "Promise" is successful and everything carries on normally. If an error happens, it is caught and sent on via the "next" function where the Express Error Handler will pick it up and display it in the error view.
  7. When done, the entire route should look similar to this:
    // Index route
    app.get("/", utilities.handleErrors(baseController.buildHome))
  8. Ensure there are no warnings or errors.
  9. Save the file.
  10. An Explanation

    • Line 1 - a route comment which should already exist.
    • Line 2 - app.get is an Express route handler, watching for the base route "/", with no other URL elements.
    • Line 2, utilities.handleErrors(baseController.buildHome) - the middleware function that catches any errors generated and sends them to the Express Error Handler.
    • Line 2, ) - closes the app.get function.

Break the Controller

In order to test the middleware, we need to cause an error to occur. Let's go break something (only temporarily).

  1. Open the baseController file.
  2. Comment out the line which calls the "getNav()" function inside the "buildHome" function.
  3. Save the file and make sure all other files are saved.
  4. Just as you have done many times, open the VSC terminal and start the server if it's not already running.
  5. Open the application in a browser tab - "localhost:5500/", press "Enter".
  6. You should be looking at the error view, not the home view. The reason, I hope is clear, is because the nav is required by the view, but could not be created due to being commented out.
  7. If, you do receive the home view, and it has a navigation bar, it may be a "cached" page. Hold down the "Shift" key and click the "Reload" button in the browser to bypass the cached page and request a new version from the server.
  8. If it worked, then you're on your way.
  9. Return to the baseController, remove the comment, and save the page.
  10. Go back to the browser window and attempt to go the home view again. This time it should work.

Revise the Express Error Handler

As it is right now, the error view displays the error message, such as "nav is undefined". While that may seem innocent enough, it tells a potential hacker that a variable exists and that could be used in an attack. As a result, we want our error messages to be generic and not give away any information that could be used against us. For our purposes, we have already added an error log to our handler, so we know what the error message is, but we want nothing like this for the browser.

  1. Find and open the server.js file, if not already open.
  2. Scroll down and locate the "Express Error Handler", near the bottom of the file.
  3. Alter the function to look like this:
    /* ***********************
    * Express Error Handler
    * Place after all other middleware
    *************************/
    app.use(async (err, req, res, next) => {
      let nav = await utilities.getNav()
      console.error(`Error at: "${req.originalUrl}": ${err.message}`)
      if(err.status == 404){ message = err.message} else {message = 'Oh no! There was a crash. Maybe try a different route?'}
      res.render("errors/error", {
        title: err.status || 'Server Error',
        message,
        nav
      })
    })
  4. The majority of the code remains unchanged. Let's look at the two lines that did change.
  5. Line 8 - this line checks to see if the status code is 404. If so, the default error message - "File Not Found" - is assigned to the "message" property. If it is anything else, a generic message is used. Feel free to alter the generic message if desired.
  6. Line 11 - only the generic message (set in line 8) is sent, rather than the error.message as existed before.
  7. Ensure that no warnings or errors exist.
  8. Save the file.

Time to Test

  1. Go back to the baseController, and break the "buildHome" function, just as you did previously.
  2. In the browser, navigate to the home view. Again, you should be looking at the error view, but with a different, generic, error message as set in the Express Error Handler function.
  3. Try navigating to a route that doesn't exist (e.g. localhost:5500/darthvader). You should be returned to the error view, but this time with the 404, File not found code and message displayed.
  4. I hope everything works!
  5. If so, go back to the baseController and fix the error. Then save the file.
  6. If things didn't work, get help and keep at it until it does.

Conclusion

With the higer order function and Express error handler in place and tested, you are ready to implment them throughout the application. You'll do that later in this lesson.