Custom Exception Handling with NodeJS
Errors are thrown by the engine, and exceptions are thrown by the developer.But what exactly does this mean?
In JavaScript, all exceptions are simply objects while the majority of exceptions are implementations of the global Error class.
There are two ways to throw an exception:
- Directly via an error object
- Through a custom error
Error Objects
When an exception occurs, an object representing the error is created and thrown. The JavaScript language defines seven types of built-in error objects. These error types are the foundation for exception handling. Each of the error types is described below.
Errors: are used to represent generic exceptions. This type of exception is most often used for implementing user defined exceptions.“Error” objects are instantiated by calling their constructor as shown in the following example.
let error = new Error("error message");
RangeError:
Exceptions are generated by numbers that fall outside of a specified range.
let pi = 3.14159;
pi.toFixed(100000); // RangeError
ReferenceError:
Exceptions are thrown when a non-existent variable is accessed. These exceptions commonly occur when an existing variable name is misspelled.
let foo = () => {
bar++; // ReferenceError
}
TypeError:
Exceptions occur when a value is not of the expected type. Attempting to call a non-existent object method is a common cause of this type of exception.
Let foo = {};
foo.bar(); // TypeError
URIError:
The URIError object represents an error when a global URI handling function is used in a wrong way.
decodeURIComponent("%"); // URIError
EvalError:
The global EvalError contains no methods of its own, however, it does inherit some methods through the prototype chain.
The Need for Custom Exceptions
The main reasons for introducing custom exceptions are:
- Business logic exceptions - Exceptions that are specific to the business logic and workflow. These help the application users or the developers to understand the exact problem.
- To catch and provide specific treatment to a subset of existing JavaScript exceptions
Custom Exceptions:
When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks.
For errors in network operations we may need HttpError, for database operations DbError, for searching operations NotFoundError and so on.
Our errors should support basic error properties like message, name and, preferably, stack. Moreover, they may also have other properties of their own, e.g. HttpError objects may have a statusCode property with a value like 404
or 422
or 500
.
That class is built-in, but here’s its approximate code so we can understand what we’re extending:
// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (different names for different built-in error classes)
this.stack = <call stack>; // non-standard, but most environments support it
}
}
Here's how you could create custom error classes in Node.js using the latest ES6 / ES2015 syntax.
Defining our own base class for errors
Exceptions/AppError.js
class AppError extends Error {
constructor(message, httpCode = 500) {
super(message);
this.status = httpCode;
}
}
export default AppError;
Exceptions/ValidationError.js
class ValidationError extends Error {
constructor(validationErrors, httpCode = 422) {
super();
this.validationErrors = validationErrors;
this.status = httpCode;
}
}
export default ValidationError;
Throwing and Catching
Services/validateService.js
import ValidationError from "../Exceptions/ValidationError";
import AppError from "../Exceptions/AppError";
const validate = async (data, schema) => {
try {
const valid = await validateFunction(data);
return valid;
} catch (err) {
if (err instanceof ValidationError) {
throw err;
} else {
throw new AppError(err.message);
}
}
};
export default { validate };
Conclusion:
We can inherit from Error and other built-in error classes normally. We just need to take care of the name property and don’t forget to call super.We can use instanceof to check for particular errors. But sometimes we have an error object coming from a 3rd-party library and there’s no easy way to get its class. Then name property can be used for such checks.