It is not hard to see that some people are struggling to handle errors, and some are even totally missing it. Properly handling Node.js errors not only reduces the development time but also helps finding bugs easily in a large-scale application codebase.
In particular, Node.js developers sometimes find themselves working with not-so-clean code while handling various kinds of errors, incorrectly applying the same logic everywhere to deal with them. They just keep asking themselves, “Is Node.js bad at handling errors? Or if not, how should we handle them?” My answer to them is “No, Node.js is not bad at all. That depends on us developers.” Our favorite method for dealing with this is straightforward but efficient.
In this series, we will explore eight crucial software development aspects: project architecture, error handling, code style, testing, quality assurance, preparing for production deployment, security measures, performance optimization, and Docker usage.
Node.JS Best Practices
Part 2
Part 3
Code Style Best Practices
Part 4
Testing & Quality Practices
Part 5
Ready to Production Practices
Part 6
Security Best Practices
Part 7
Performance Practices
Part 8
Docker Best Practices
Part-2: Error Handling Practices
In this article, our focus will be on Error Handling Practices.
2.1 Node.js Error Handling: Error Types
- Synchronous errors
- Asynchronous errors
- Operational errors
- Programmer errors
Synchronous errors
- Synchronous error refers to an error that occurs during the execution of synchronous code, meaning the code runs line by line and blocks the main thread until the current operation is complete.
Asynchronous errors
- These are found in callbacks, promises, or even in asynchronous functions. This is one scenario where try-catch won’t be directly usable and needs to be handled by some other approach, such as error-first callbacks or promises.
Operational errors
Operational errors represent runtime problems, which need to be dealt with in a proper way. Operational errors don’t mean the application itself has bugs, but developers need to handle them thoughtfully.
Some examples of operational errors are:
- Out of memory
- Connecting to the server
- Request timeout
- Socket hang-up
Programmer errors
- This means unexpected bugs in poorly written code. The code contains errors that need to be resolved and was written incorrectly. An exemplary case is experimenting with the value of undefined. To rectify the problem, the code must be modified. The developer is responsible for the bug, not an operational error.
2.2 Error in Node.js
Node.js has multiple methods for passing and managing errors that arise during the execution of an application. The way these errors are reported and dealt with depends entirely on the type of error and the style of the API that is used.
When JavaScript faces an error, it treats it as an exception and immediately raises an error using the standard JavaScript throw method. The try/catch construct in JavaScript is used to handle exceptions.
Here is the general way to throw errors in Node.js:

2.3 Best Practices for Error Handling
2.3.1 Use try-catch for Synchronous Errors:
Any use of the JavaScript throw mechanism will raise an exception that must be handled using try/catch, or the Node.js process will exit immediately.
- Use try-catch for operations that might throw errors.
- Ensure errors are caught and handled appropriately within the same function.

2.3.2 Utilize async-await or Promises for Effective Async Error Handling:
Most asynchronous methods exposed by the Node.js core API follow an idiomatic pattern referred to as a callback. With this, a callback function is passed to the method as an argument. When the operation either completes or an error is raised, the callback function is called with the Error object (if any) passed as the first argument. If no error was raised, the first argument will be passed as null.
The JavaScript try/catch mechanism cannot be used to intercept errors generated by asynchronous APIs. A common mistake for beginners is to try to use throw inside a callback.
Code examples:

Using chained promise

Using async/await to catch errors
2.3.3 Stick to the Built-in Error Object:
The Javascript engine throws a built-in error objects when something goes wrong in the runtime of code execution. These error objects capture a “stack trace” of the error with helpful information. It contains a human-readable error message, reference of the error type and some other useful information. Stack trace helps to navigate to the root of the error.

2.3.4 Distinguish operational vs programmer errors:
Distinguishing the following two error types will minimize your app downtime and help avoid crazy bugs: Operational errors refer to situations where you understand what happened and the impact of it – for example, a query to some HTTP service failed due to connection problems. On the other hand, programmer errors refer to cases where you have no idea why and sometimes where an error came from – it might be some code that tried to read an undefined value or DB connection pool that leaks memory. Operational errors are relatively easy to handle – usually logging the error is enough. Things become hairy when a programmer error pops up. The application might be in an inconsistent state, and there’s nothing better you can do than restart gracefully.