Error Management in Node.js Applications

No one is perfect in this world including machines. None of our days pass without having errors faced in our professional life. Whenever we are facing any issues/errors while working rather than worrying, let us all fix our mind like we are going to learn something new. This will make your tasks easier.

One of our friend Error Handling will help us in fighting with these errors. These reduce our pressure by finding the errors and guide us to achieve the desired output.

It might be a tiresome task in Node.js but don’t worry here are a few best practices about error handling in Node.js learn it and master in error handling methods.

Let’s walk through into the article to learn more about it.

  1. What is Error Handling in Node.js?
  2. Types of Errors
  3. What are error objects?
  4. Best Practices to Handle Errors in Node.js

1. What is Error Handling in Node.js?

While talking about errors first know what errors are and then we can learn about error handling and the best practices in it.

Error is nothing but an abnormal working of the program which causes an unexpected or incorrect result. These errors are detected only when you compile or execute a program.

Some people might detect and fix the bugs easily while others might find it difficult to handle the errors and some might totally miss all the errors. Handling errors helps you in reducing the development time by detecting the bugs and resolving them quickly.

Many node.js developers find error handling a difficult process and they keep asking themselves “Is Node.js bad at handling errors?

Here is my answer, Yes it is hard for any beginner to handle errors while it might become easier upon practicing it often.

Due to the asynchronous nature of node.js detecting and handling errors might be a pain point for developers.

Take a look at my article and see what are the different ways to handle errors in Node.js.

2. Types of Errors

There are two main types of errors in node.js. Errors can be operational or programmer. See what they are and how they occur in your code.

i) Programmer Errors

Programming errors also known as bugs or faults which represent unexpected issues in a poorly written code. It means the developer who built the code made mistakes and should be fixed as soon as possible since it might cause poor end-user experience.

Some of the basic instances that cause programming errors.

  • Asynchronous function without a callback.
  • Passing an object where a string is required.
  • Reading an undefined property.
  • Passing incorrect parameters to a function
  • Did not resolve a promise

ii) Operational Errors

These are run-time errors which usually occur when there is a problem with your system, system configurations, the network or the remote services.  These errors should be fixed in a proper way.

Some of the operational errors include:

  • Request timeout
  • Invalid user input
  • Socket hang up
  • Out of memory
  • Failed to connect DB server

These are the different types of errors which you will encounter while building your application with node.js.

The major reason behind dividing errors into these two categories is

  1. You might think of restarting your app if there is an error user not found error? Definitely not since other users might enjoy using your application which is an example of operational error.
  2. You must restart your app when you fail to catch an rejected promise since this bug might threaten your app which is an example of programmer error.

3. What are error objects?

Error Object is either the instance of the object or extends the Error class which is a built-in object in the node.js runtime. It gives a set of error information when used properly.

Example:

const error = new Error("Error message");
console.log(error);
console.log(error.stack);

Output:

{ stack: [Getter/Setter],
  arguments: undefined,
  type: undefined,
  message: 'Error message' }
Error: The error message
    at Object.<anonymous> (/home/nico/example.js:1:75)
    at Module._compile (module.js:407:26)
    at Object..js (module.js:413:10)
    at Module.load (module.js:339:31)
    at Function._load (module.js:298:12)
    at Array.0 (module.js:426:10)
    at EventEmitter._tickCallback (node.js:126:26)

The above code shows the complete stack trace of an error and also you can view the functions that were called before the error occurred.

You can also add more properties if you want to know some more information about the Error object.

const error = new Error("Error message");
error.status_code = 404;
console.log(error);

4. Best practices to Handle Errors in Node.js

As I said before, error handling in node.js is a troublesome task. It would take much time to achieve this stage.

Before entering into how to handle errors in node.js in a best way you should know how Node.js architecture, frameworks and libraries work with all the developer practices.

Do not repeat your mistakes rather handle them with utmost care to resolve them faster. Here are some of the best ways to handle all your errors in your application.

i.) Handling asynchronous errors

It is quite tricky to handle errors in an asynchronous code if you’re not quite familiar with it. There are three ways you can handle asynchronous operations.

a.) Using Promises

Promises are the new and improved way of writing asynchronous code which can be used to replace callback methods.

Use .catch() while handling errors using promises.

Usage:

doSomething1()
  .then(doSomething2)
  .then(doSomething3)
  .catch(err => console.error(err))

Example:

function myAsyncFunction() {
   return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('oops'))
      }, 1000);
   })
}

myAsyncFunction()
  .then(() => {
      // happy path
  })
  .catch((err) => {
      // handle error
  })

b.) Using Callbacks

Callbacks were the most basic and oldest way of delivering the asynchronous errors.

The callback function can be passed as the parameter to the calling function, which you later invoke when the asynchronous function completes executing.

Usage:

callback(err, result)

Example:

function myAsyncFunction(callback) {
  setTimeout(() => {
    callback(new Error('oops'))
  }, 1000);
}

myAsyncFunction((err) => {
  if (err) {
    // handle error
  } else {
    // happy path 
  }
})

c.) Using async-wait

To catch errors using async-wait method you can do it this way:

function myAsyncFunction() {
   return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('oops'))
      }, 1000);
   });
}

(async() => {
  try {
    await myAsyncFunction();
    // happy path
  } catch (err) {
    // handle error
  }
})();

ii.) Use a Middleware

You can configure a centralized error handling method when you have a set of custom errors. To catch all the errors you can use middleware and from there you can decide whether to log all the errors or you need to get notified whenever an error occurs.  

To forward the errors to the error handler middleware use the next() function.

Usage:

app.post('/user', async (req, res, next) => {
  try {
    const newUser = User.create(req.body)
  } catch (error) {
    next(error)
  }
})

iii.) Catch Uncaught Exceptions

Your application will crash if an uncaught exception is thrown during the execution of your code.

Even after handling the most crucial errors in your app you might end up missing a few errors which could lead to an uncaught exception.

Listen to the event uncaughtException emitted by the process object and this might also cause unexpected effects in your application. But if you are listening to these events you should follow the below steps.

  • Log the error information so that you can take a look at it later.
  • Exit your application forcefully to launch a replacement process.

It is an example on bad way of writing the uncaught exception code.

process.on('uncaughtException', (err) => {
  logger.fatal('an uncaught exception detected', err);
});
  
process.on('unhandledRejection', (err) => {
  logger.fatal('an unhandled rejection detected', err)
});

This is an example of good way of writing it.

process.on('uncaughtException', (err) => {
  logger.fatal('an uncaught exception detected', err);
  process.exit(-1);
});
  
process.on('unhandledRejection', (err) => {
  logger.fatal('an unhandled rejection detected', err);
  process.exit(-1);
});

iv.) Catch Unhandled Promise Rejections

When a promise is rejected it always looks for a rejection handler,if it finds one it calls the function with the error.

Handle them properly by using fallback.

process.on(“unhandledRejection” , callback)

Example:

...
const user = User.getUserById(req.params.id)
 .then(user => user)
 // missing a .catch() block
...

// if the Promise is rejected this will catch it
process.on('unhandledRejection', error => { 
  throw error
})

process.on('uncaughtException', error => {
 logError(error)

 if (!isOperationalError(error)) {
   process.exit(1)
 }
})

v.) Use the appropriate log levels for errors and error alerting

You should not only choose the best logging library to log your errors instead you should also know how well you can use it to log all the error information that you catch.

You can also log all the error messages at different log levels which can then be sent to different destinations such as stdout, syslog, files etc.

You should also opt the perfect log levels for your message based on the priority of log messages.

Here are some of the basic log levels which could be used often.

  • log.info - If informative messages occur frequently it could become a noise. These messages are used for reporting significant successful actions.
  • log.error - All critical error messages which require instant attention and could possibly cause any dire consequences.
  • log.warn -  This warning message occurs when something unusual happens which is not critical but it could be useful if we review it and resolve the error.
  • log.debug - These messages are not very crucial but could be useful while debugging.

Manage your errors in Node.js with Atatus

We at Atatus, offer you a comprehensive view on the errors that occur in your application. You can also log all the errors that rise in your application which can be used later.

Atatus captures all the errors and provides you detailed insights on error messages with a complete stack trace.

Along with Error tracking, Atatus provides features such as:

Sign up with Atatus and get a 14-day free trial.

Final Words

Take the right approach before handling errors in your application. It is bit tricky handling the errors in Node.js if you do not know the source of the errors. Since node.js is an emerging technology you should know in-depth knowledge on how it works, the frameworks in it and the possible ways to handle the errors that your application might throw.

Make your application robust to impress your users by building a proper error-handling system. Also monitor your errors frequently with any monitoring tools to fix the issues at a faster rate.

Share your thoughts with us in the below comment section!!!

Vaishnavi

Vaishnavi

CMO at Atatus.
Chennai

Monitor your entire software stack

Gain end-to-end visibility of every business transaction and see how each layer of your software stack affects your customer experience.