It’s important to understand error handling in any programming language, but especially Node.js. If your API isn’t built to handle unexpected values, the server will crash and will need to be restarted. Think about a scenario where you have an app that accepts user input and people from all over the world are using the app. There’s bound to be cases where special/foreign characters are entered, and you need to be able to handle them.
On top of this, applications can be complex and are almost always written by multiple people. Issues can be hard to find, proper error handling could be the difference between solving a bug in a few hours versus solving it in a few days. Not to mention the users getting a message like “Cannot read property of undefined” compared to “An error occurred. Please contact tech support.”
In this post, I will talk about the best ways to handle errors in Node so that your application is robust and bug-free (as possible).
JavaScript Error Object
I was confused about whether the Error object in Node.js was the same as the one in JavaScript. Apparently it is, as it is defined in the docs as:
A generic JavaScript Error object that does not denote any specific circumstance of why the error occurred. Error objects capture a “stack trace” detailing the point in the code at which the Error was instantiated, and may provide a text description of the error.
When runtime errors occur in JavaScript, execution of the current function stops and an Error object is thrown. Control will be passed back to the first catch function in the call stack. If no catch is found in the caller functions, the program will terminate.
The Error constructor has a few variations; the one I’ve used is Error(message) which accepts a string representing the error message. Additionally, Error objects have a name property that represents the name of the constructor that the error belongs to. The base object’s initial value is “Error” while subclasses like ReferenceError and SyntaxError have their own name values. Error objects also have other, non-standard, properties that depend on the browser or environment being used.
Custom Errors
We can define custom errors in JavaScript by extending the Error class. Why would we want to do this? Many times in development, we need an error to reflect some potential issue in our application. Maybe we want a DbError for a database issue or a NotImplementedError to indicate that a method has not been implemented.
To do this, it is pretty simple:
There is a bit of code here, but the point is we can extend the Error object and tailor it to our needs. Also, unintentionally, I implemented a static class here (a solution I found online as I didn’t know they were possible in JavaScript); we could extend it as well if we wanted to (you could do this as an exercise for yourself).
Error Types
Node.js errors can be classified into two groups: operational and functional.
Operational Errors
These errors occur during runtime. They aren’t bugs, but are expected and should be handled properly. They can be caused by things like:
- memory leaks
- invalid user input
- incorrect configurations
Functional Errors
These errors are referred to as programmer errors or bugs. They are caused by mistakes in the logic or syntax of the code. We don’t really handle these, instead , we try to avoid them altogether. When a programmer makes a mistake like this, they will usually use a debugger to step through the code to identify where the issue lies.
How the App Can Crash
I recently worked on a project where I was building the backend to a web application with Node/Express. While working on it, I encountered a situation that caused the app to crash and need to be restarted.
I was working with an API controller method that looked like this:
When attempting to read a valid location, everything worked okay:
However, when attempting to read a location that didn’t exist, the app would crash:
I learned that Express will handle errors that occur in synchronous code in route handlers, but errors that occur in asynchronous code require more work. To resolve this, I used the async-express-handler middleware to wrap my route handlers so Express could handle them:
The point is that our app can crash when we don’t have measures in place to handle errors. Express comes with a default error handling middleware, but let’s look at how we can create our own.
Express Error Middleware
Out of the box, Express comes with an error handler. However, we can also create a custom error handler and tailor it to our needs. The advantage of this is we can have our error logic consolidated in one place instead of having to repeat it throughout all of our controller methods.
To start, create a file called errorMiddleware.js. I put mine in a middleware folder in the same location as my main file, server.js:
Next, we need to bring that into our main file:
It should go after our other app.use() and route calls:
If a response status has not been set, our error middleware will set it to 500 by default. Additionally, the default express error handler returns an HTML page:
While ours returns a JSON object:
Inside of our error middleware, we have access to the err, req, res and next() arguments and can use them how we need to.
- err: This is our Error object (the same one we talked about in the beginning). We can glean information from it like name and stack like we did in the example.
- next(): This is used to call the next middleware for the given route.
If you’ve used Express, then you should be familiar with req and res so I won’t go into them here. This was a brief explanation of Express error middleware, you can check out the docs here for more info.
Conclusion
Hopefully this post has given you a better understanding of how errors work in JavaScript and Node.js and how to handle them. If you have any questions or comments, feel free to reach me via the contact page.