A clean approach of node.js error handling

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

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.

Generate Error

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/');Code language: JavaScript (javascript)

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

  1. Traditional Callback Approach
  2. Promises
  3. async/await
  4. Promise Wrapper
  5. From Express.js Server (Bonus Approach with Express.js)

Project Setup

  • 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-nativeCode language: JavaScript (javascript)

Callback Approach (The Ugly)

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);Code language: JavaScript (javascript)

 In this callback approach, there is always a big irritating issue of callback-hell.

Using Promise (The Bad)

When we are using a promise, we can simply chain the catch() method.

SomeTask()
  .then()
  .then()
  .catch(error => throw error);Code language: JavaScript (javascript)

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,

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/');Code language: JavaScript (javascript)

async/await (The Bad)

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 = async url => {
  try {
    await request.get(url);
  } catch (error) {
    console.log(error);
  }
};
 
requestWrapper('https://www.some-unknown-url-1234.com/');Code language: JavaScript (javascript)

 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.

Promise Wrapper(The Good)

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
};Code language: JavaScript (javascript)

 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/'); Code language: JavaScript (javascript)

Error Handling Middleware (For Express.js)

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);Code language: PHP (php)

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)
  });Code language: JavaScript (javascript)

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.

Wrap Up

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 possible. Also, you can contact iXora team for any assistance.

Add a Comment

Your email address will not be published. Required fields are marked *