MEN Stack Error Handling Server-Side Error Handling

Learning objective: By the end of this lesson, students will understand the role of server-side error handling.

What is server-side error handling?

Server-side error handling refers to the process of managing errors that occur within the server of an application. The server is responsible for a variety of operations, and robust server-side error handling is crucial for the stability and security of any application.

Below are a few benefits of effective server-side error handling:

Error handling with try...catch

One of the essential mechanisms for error handling on the server is the try...catch block. When using a try...catch block, the program first attempts to execute code written inside the try block. If an error occurs, control is passed to the catch block where we can place code that handles and resolves the error.

In the context of server-side error handling, try...catch blocks are quite versatile and can help manage a range of issues including:

Practical example

Let’s see a practical example of how a try...catch block could improve our application.

Take a look at our fruitSchema in models/fruit.js:

// models/fruit.js
const fruitSchema = mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
});

Notice how the name property is required. This prevents empty name strings from being added to the database by triggering a validation error if the condition is not met.

Let’s update our controller function for creating fruits so it handles this validation error:

// controllers/fruits.js
router.post("/", async (req, res) => {
  try {
    await Fruit.create(req.body);
    res.redirect("/fruits");
  } catch (err) {
    console.log(err.message); // Logs the error message
    res.send("An error has occurred.  Go back and try again.");
  }
});

Now, when a user submits a form without filling out the name field, the resulting validation error is dealt with in our try...catch block.

When the operation in the try has failed, control is passed to the catch block, where it receives an Error object as an argument. The Error object will contain a message property with details about the error that occurred.

If the name field is ever missing from the form data, the terminal would display the following message:

Fruit validation failed: name: Path `name` is required.

The catch block will also send a response to client, informing the user that an error has occurred.

However, it’s important to note that try...catch blocks aren’t perfect. There are many issues that will go undetected if a try...catch is the only safeguard applied. This leads us to the need for custom error handling.

Custom error handling

With custom error handling, we can address more complex or specific error conditions that the standard try...catch might miss. Custom errors also allow us to write our own error messages that improve the user experience and aid in the process of debugging.

Custom error handling can be useful when:

Practical example

Let’s see an example of custom error handling at work.

Imagine a scenario where a user fills out a form but accidentally submits it with a name field that contains only blank spaces. While technically not empty, we should consider this invalid input. However, this kind of input does not trigger a validation error based on our current schema, which only checks for the existence of a value, not its content.

Here is how we could modify our code to handle this edge case:

// controllers/fruits.js
router.post("/", async (req, res) => {
  try {
    if (!req.body.name.trim()) {
      throw new Error("Invalid input: The name field cannot be empty!");
    }
    await Fruit.create(req.body);
    res.redirect("/fruits");
  } catch (err) {
    res.render("fruits/new.ejs", { errorMessage: err.message });
  }
});

Notice the if statement in the example above.

The trim() method returns a new string with the white space removed. We’ve applied the method to the name property of req.body. If the name field is invalid, we create and throw a new error with a custom error message.

When we throw an error, control passes immediately to our catch block, where the error message is passed to our view. As a result, the create() method is never called upon, and the invalid name input is never added to the database.

Finally, we can display the errorMessage for users by adding the following to our view:

<!-- views/fruits/new.ejs -->
<% if (typeof errorMessage !== 'undefined') { %>
<p><%= errorMessage %></p>
<% } %>