Propper way of Error handling in a nodejs API server

Tags: , , ,



I need to handle errors in my API server on node.js. I created a error handling module, which sends the errors (only in dev mode) to the API client in a JSON object similar to this:

{
    "status": "fail",
    "error": {
        "statusCode": 404,
        "status": "fail",
        "isOperational": true
    },
    "message": "no valid register found. Please provide a valid register",
    "stack": "Error: no valid register found. Please provide a valid registern    at /Users/myUser/ITstuff/smarthome-heating/controllers/modbusController.js:77:21n    at async /Users/myUser/ITstuff/smarthome-heating/controllers/modbusController.js:73:17"
}

now the problem I have is, that I need to create a new error in a submodule of my API controller when no case is true in the switch statement.

// this is the modbusHandler.setValue(doc, val, next) function
// 2) RUN PROGRAMMS
  let checkVal;
  switch (doc.register) {
    case 0:
      await client.writeCoil(doc.address + offset, !!val);
      checkVal = await client.readCoils(doc.address + offset, 1);
      break;
    case 4:
      await client.writeRegister(doc.address + offset, val);
      checkVal = await client.readHoldingRegisters(doc.address + offset, 1);
      break;
    default:
      throw new Error(
    'no valid register found. Please provide a valid register'
  );
  }

At this time, I handle it in this way:

// the function is wrapped in a try-catch statement
     const val = await modbusHandler.setValue(doc, req.body.value, next).catch((err) => {
                    console.log('here it is');
                    return next(new AppError(err.message, 404));
                  });


res.status(200).json({
    status: 'success',
    data: {
      val,
    },
  });
  1. In the API controller, the function with the switch statement gets called
  2. If no case matches the expression, then it throws a new Error
  3. catch the error then call the custom error and respond error in a JSON format to the client

This solution works not really, I get a response error because there is no return for the function of the API controller. This causes a second response which is of course not good.

My question is now: How can I solve this problem in the right way?

Custom Error constructor:

class AppError extends Error {
  constructor(message, statusCode) {
    // this is the official error message rom the error it self, this message will be in the response.json for the client
    super(message);

    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;

    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;

Custom Error handler:

module.exports = (err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  if (process.env.NODE_ENV === 'development') {
    sendErrDev(err, res);
  } else if (process.env.NODE_ENV === 'production') {
    sendErrProd(err, res);
  }
};


const sendErrDev = (err, res) => {
  res.status(err.statusCode).json({
    status: err.status,
    error: err,
    message: err.message,
    stack: err.stack,
  });
};

Answer

Instead of throwing the error, perhaps want to throw it to next like this so it can be handled by the error handler.

export const yourFunction = async (req, res, next) => {
   .....
   // throw the error to your route Error handler 
   return next(new AppError('no valid register found. Please provide a valid register', 400)) 
}

Then after all your route declaration you should have an Error handler that looks something like.

app.use(ErrorHandler);

You may also need an error catcher

//catchAsync
module.exports = fn => {
    return (req, res, next) => {
      fn(req, res, next).catch(next);
    };
  };

Which you will wrap your route like this.

route.get('/path/', catchAsync(yourFunction))

If you create the catchAsync middleware, you will not need any Try/Catch in your routes at all, as all will be thrown to your error handler.

Update.

The small complain about node, or even javascript is about the error handling. For every function of function of function called, if you choose to throw error, then you need to throw error of error of error. You keep bubbling up “throw error” and then it gets out of hand.

In your switch statement, I would recommend you to return a null.

then you test `if (!variable) return next(new AppError);

Helper functions should behave like a helper function, it should return true/false/null/value, and then you determine in your main function whether an error should be thrown or not.

This way you centralises your error.



Source: stackoverflow