Some error handling techniques in Node.js

Some error handling techniques in Node.js

Daily short news for you
  • openai/codex is the latest open-source project from OpenAI, following their announcement of the two newest models, o3 and o4 mini. It is said that both o3 and o4 mini are very suitable for being Agents, so they released Codex as a lightweight Agent that runs directly in the Terminal.

    Regarding its applicability, since it is an Agent, it can read/add/edit/delete the contents of your files. For example.

    codex "explain this codebase to me"

    Or integrate it into your CI/CD pipeline.

    - name: Update changelog via Codex run: | npm install -g @openai/codex export OPENAI_API_KEY="${{ secrets.OPENAI_KEY }}" codex -a auto-edit --quiet "update CHANGELOG for next release"

    Oh, I almost forgot, you need to use the OpenAI API 😆

    » Read more
  • Perhaps many people do not know that OpenAI has launched its own academy page to help users learn and fully harness the power of their language models.

    OpenAI Academy

    » Read more
  • Mornings have started with some sensational news: OpenAI wants to acquire Windsurf for $3 billion 😳

    » Read more

The problem

Errors are a constant presence in application development. There's a saying that as long as there is code, there will always be "bugs". There are obvious errors that we can anticipate, and then there are those unknown errors that can catch us by surprise.

Errors can cause inconvenience and sometimes even have serious consequences. Therefore, error handling is always an important aspect of programming. In this article, I will present some "elegant" error handling techniques in the Node.js environment.

Techniques

Use async/await or promises to handle errors in asynchronous functions

Avoid using callbacks that can result in callback hell, making your code deeply nested and hard to read and maintain.

Here's an example of using callbacks to handle errors:

getData(someParameter, function(err_a, a) {
    if(err_a!== null) {
        getMoreDataA(a, function(err_b, b) {
            if(err_b !== null) {
                getMoreDataB(b, function(err_c, c) {
                    getMoreDataC(c, function(err_d, d) {
                        if(err_d !== null ) {
                            // do something
                        }
                    })
                });
            }
        });
    }
});

Instead, use promises to handle errors in a more "elegant" way:

return getData(someParameter)
    .then(getMoreDataA)
    .then(getMoreDataB)
    ...  
    .catch(err => handle(err));

However, promises can make debugging difficult. Use the "graceful" syntax of async/await combined with try/catch:

try {
    const a = await getData(someParameter);
    const b = await getData(someParameter);
} catch(err) {
    handle(err);
}

Throw an Error object instead of any other object when reporting an error

JavaScript allows us to "throw" any object to report an error, whether it's a number, a string, or an object. However, it's best to throw an Error object to ensure consistency in your code and with libraries. Furthermore, an Error object retains important information such as the StackTrace, which indicates where the error occurred.

// don't do this
if (!condition) {
    throw ('invalid condition');
}

// do this instead
if (!condition) {
    throw new Error('invalid condition');
}

// or you can even do better by creating a custom error object
// that carries more useful information
function MyError(name, description, ...args) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.name = name;
    this.description = description;
    ...  
};

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

if (!condition) {
    throw new MyError('CONDITION_NOT_VALID', 'invalid condition');
}

Centralize error handling

Create one or more functions that are ready to receive an error object and then analyze or distribute the error to different places. Avoid handling errors separately, as this can make your code harder to control.

Imagine what you would do when an error occurs. Do you log it to the console, send it to monitoring or logging services, write it to a file, check conditions and classify the error... there are many things to do with an error once it is thrown. Therefore, gather them in one or more specialized error handling functions.

Beware of unhandledRejection errors

This is a very annoying error in Node.js that can cause your application to hang and no longer process any requests. This error occurs when you mishandle a promise, specifically when there is no error handling function for reject in the promise.

UserModel.findByPk(1).then((user) => {
  if(!user)
      throw new Error('user not found');
});

To catch this error, we need to use process.on.

process.on('unhandledRejection', (reason, p) => {
  throw reason;
});

process.on('uncaughtException', (error) => {
  errorManagement.handler.handleError(error);
  if (!errorManagement.handler.isTrustedError(error))
    process.exit(1);
});

process.on is usually placed in the index.js file, which is the first file that your application starts with, so that it can "listen" to any signals coming from mishandling promises. This allows you to handle the error properly and prevent the server from hanging.

Never trust input data, always validate

We provide POST, PUT... endpoints to allow clients to send data to our application, but the data cannot always be trusted. Therefore, the safest approach is to always validate the received data to prevent unintentional or malicious submission of incorrect data, which can cause errors or even system breaches.

There are many excellent libraries that can help with this, such as Joi or ajv.

const schema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required(),  

  password: Joi.string()
    .pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),  
});

let body = { username: '2coffee', password: '2coffee' };
schema.validate(body); // -> { value: { username: '2coffee', password: '2coffee' } }

let body = {};
schema.validate(body); // -> { value: {}, error: '"username" is required' }

Use a professional logging system

Logging is a way to track and monitor errors that have occurred in the past, allowing you to retrieve them at any time. I have a separate article on logging in Node.js applications, which you can read at Logging in Node.js Applications at 3 Different Levels.

Write unit tests

Unit tests are one of the methods that help you catch errors early in the development process. While writing unit tests can be time-consuming, it is worth investing your time in them. Once your unit tests are working well, they will save you a lot of time and cost in the long run.

"Exit" the application when necessary

In cases where you encounter specific errors for which there is no immediate solution or recoverable state, the temporary solution is to crash the application and rely on DevOps tools to restart it. This may not be the best approach, but restarting your application will restore it to the state before the error occurred.

Use APM (Application Performance Management)

Application Performance Management (APM) is used to monitor and manage the performance and availability of an application. APM tries to detect and diagnose performance issues and promptly notify you when there's a problem.

There are many APM products and services in the market, ranging from open-source to paid solutions. The features vary from simple to complex, from basic monitoring to deep integration for monitoring complex systems.

Start with a simple APM service like uptimerobot.com. It monitors the uptime of your application by sending a request every 5 minutes and waiting for a successful or failed response.

uptimerobot.com

New Relic is a more comprehensive tool that monitors various aspects such as detailed error stack traces, response times, performance bottlenecks, and detailed statistics.

New Relic

Conclusion

These are some error handling techniques that I recommend for Node.js applications. There may be other techniques that I'm not aware of, so if you discover any missing techniques, please leave a comment below.

Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (1)

Leave a comment...
Avatar
Jess Vanes2 years ago
Hướng dẫn dùng mấy tool apm đi ạ
Reply
Avatar
Xuân Hoài Tống2 years ago
@gif [ISOckXUybVfQ4] Chắc là sẽ hơi lâu ạ