Node.js Error Handling (The Good, The Bad, The Ugly)

A lifelong learner. Love to travel. Listen to music.
Search for a command to run...

A lifelong learner. Love to travel. Listen to music.
No comments yet. Be the first to comment.
Get the source code from here. Introduction As a developer, you have to spend quality time for monitoring, troubleshooting, and debugging. Logging makes it super easy, fast and makes these process smoother. Logging is a perfect way to keep applicatio...
Best Practices for Path Structure, Versioning, and Error Handling

Understanding the Performance Implications of async and defer Attributes

Overview static: Default position. It does not allow setting properties like top, bottom, left, right, z-index relative: Acts the same as static. but allows positioning it relatively by putting properties like top, bottom, left, right, z-index absolu...

Overview When an element in the browser has a conflict of styles, the browser uses a set of rules to determine which style should be rendered. This set of rules is defined as CSS Selector Specificity. For instance, for an element, if we have the foll...

Master block, inline, flex, and grid to build better web layouts

Originally posted in iXora Solution Blog

In an ideal world, everything works successfully. However in real worlds there always unexpected errors.
For example, a connection to the database drops out for whatever reason. So as a best practice, developers should count these unexpected situations and handle them properly.
That means we should properly send an error message to the client and also log the unexpected situation.
In this article, we only cover the exception handling and also, how can we improve the exception-handle functionality.
Let me demonstrate a real-world scenario, where some exception occurs. Here, we make a get request at URL https://www.some-unknown-url-1234.com/ and expect to get status 200.
Since the URL that does not exist, and it will through some unhandled error.
const request = require('request');
const requestWrapper = url => {
request.get(url);
};
requestWrapper('https://www.some-unknown-url-1234.com/');
In this situation, the node process will be terminated, could not serve as it should.
So we need to properly handle these scenarios. And this article is all about handled these scenarios.
We will go through 4 approaches here
Traditional Callback Approach
Promises
async/await
Promise Wrapper
From Express.js Server (Bonus Approach with Express.js)
Create a project-directory and open terminal into that.
mkdir error-handling
cd error-handling
Create a node project,
npm init -y
Install dependencies,
npm i express express-async-error parse-error request request-promise-native
Here URL https://www.some-unknown-url-1234.com/ does not exist and for that, we are getting an error. To catch the error, We simply pass a callback method and handle it.
const request = require('request');
const requestWrapper = (url, cb) => {
request.get(url, (err, response) => cb(err, response));
};
const myCallBackMethod = (err, response) => {
if (err) {
throw new Error(err);
}
};
requestWrapper('https://www.some-unknown-url-1234.com/', myCallBackMethod);
In this callback approach, there is always a big irritating issue of callback-hell.
When we are using a promise, we can simply chain the catch() method.
SomeTask()
.then()
.then()
.catch(error => throw error);
Except the last catch() method We get the responses. And in the final chain method, catch(), we can grab the error if there is any. Using promise, our updated codebase will be,
A promise-chain is also another big headache. We will remove the promise and use a clean async/await format.
const request = require('request-promise-native');
const requestWrapper = url => {
request
.get(url)
.then(response => response)
.catch(err => console.log(err));
};
requestWrapper('https://www.some-unknown-url-1234.com/');
This is far better than previous promise-chain. But the problem is, each time we try to catch an error, we have to repeat the try/catch block.
To update previous async/await approach, we can write a simple promise-wrapper to remove the repetitive try/catch block.
Let’s create some utility method in utils.js
const parseError = require('parse-error');
const to = promise =>
promise.then(data => [null, data]).catch(err => [parseError(err)]);
const throwError = err => {
throw new Error(err);
};
module.exports = {
to,
throwError
};
In utils.js we got two methods, to() method returns a promise . Since it’s a promise-chain, if there’s an error occurs, it will parse and send the error.
Method throwError() will simply throw the error. In the next section, we will use a express-middleware. Then it will come handy.
Now our updated approach will be,
const request = require('request-promise-native');
const { throwError, to } = require('./util');
const requestWrapper = async url => {
[err, response] = await to(request.get(url));
err && throwError('invalid error');
};
requestWrapper('https://www.some-unknown-url-1234.com/');
In the last section, we took quite approaches to handle errors properly.
But there’s a problem with this implementation. Let’s say tomorrow we decide to change the error message, that is sent to the client. In the current implementation, we have to go through every route handler, where we use the promise wrapper and modify that message.
So we want to move the logic for handling errors somewhere central.
Then in the future, if we want to make it change, how we handle error, there will be a single place to modify.
In express, there’s a special kind of middleware error-middleware. We will register the error-middleware function after all the middleware functions.
For express let’s create the server,
const app = require('express')();
require('express-async-errors');
const errorMiddleware = require('./error');
const requestWrapper = require('./requestWrapper');
app.get('/:url', requestWrapper);
app.use(errorMiddleware);
app.listen(8080);
Make sure you are using the express error-middleware as the final middleware.
There’s left the last piece of the puzzle, The error.js
const ErrorTypeEnum = require('./errorTypeEnum');
module.exports = (err, req, res, next) =>
return res.status(500).json({
success: false,
...JSON.parse(err.message)
});
The error-middleware simply grabs the thrown error and send it back to the client.
Some advantage of error-middleware over regular error-response is,
Cental placement for error response.
Whenever we throw an error, this middleware is always there to grab the error.
Congrats!! I hope you got some clean implementation way for error handling. For any query, please leave a response below. I will reply as soon as possible. Also, you can contact iXora Team for any assistance.