Node.js Best Practices

Node.js Best Practices — Log, Test, and Lint

Spread the love

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices we should follow when writing Node apps.

Use a Logger to Increase Error Visibility

We need to make errors visible so that we can trace and fix them.

There’re many logging tools like Pino or Winston to log errors in our app.

For example, we can write:

const logger = new winston.Logger({
  level: 'info',
  transports: [
    new (winston.transports.Console)()

logger.log('info', 'log %s', 'foo', { anything: 'some metadata' });

logger.log logs the error that occurs.

Test Error Flows Using a Test Framework

Testing for error cases is just as important as testing the non-error cases.

If we test the errors, then we can see if we return the right errors or not.

For example, we can write:

it('creates an item', () => {
  const invalidGroupInfo = {};
  return httpRequest({
    method: 'POST',
    uri: '/item',
    resolveWithFullResponse: true,
    body: invalidGroupInfo,
    json: true
  }).then((response) => {'error')
  }).catch((response) => {

We test an API that throws an error with the some expect function calls.

Discover Errors and Downtime Using APM products

We can check for performance issues with APM products.

They check for slow code and list the code that is slow.

Also, we can watch for downtime if there is any.

Some tools include Pingdom, Uptime Robot, and New Relic.

They let us check for issues with easy to read dashboards.

Catch Unhandled Promise Rejections

If we have an unhandled promise rejection, then we should handle them by catching them.

For example, we can write:

Promise.reject('error').catch(() => {
  throw new Error('error');

We call catch after the Promise.reject to catch the rejected promise.

We can also catch ubhandledRejection or uncaughtException events to catch the errors.

To do that, we can write:

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

process.on('uncaughtException', (error) => {
  if (!isTrustedError(error))

We can listen to those events with the process module and then handle them our way.

reason is the reason for the rejected promise.

The 2nd callback checks for errors and then restart for some errors.

Fail Fast and Validate Arguments Using a Dedicated Library

We should check for data with a dedicated library.

For example, we can write:

const memberSchema = Joi.object().keys({
  age: Joi.number().integer().min(0).max(200),
  email: Joi.string().email()

function addNewMember(newMember) {
  Joi.assert(newMember, memberSchema);

The Joi library lets us validate our object against a schema.

We validate that age is an integer between 0 and 200.

email lets us check for email.

Then we call Joi.assert to do the check.

Use ESLint

ESLint lets us identify various issues including formatting, security, and bad constructs.

Therefore, we should use it to fix many of them automatically.

There’re other tools to format code consistently like prettier and beautify that have more powerful formatting options.

Node.js Specific Plugins

ESLint comes with various plugins that we can add to do more checks on Node apps.

They include things like eslint-plugin-node, eslint-plugin-mocha, and eslint-plugin-node-security.

eslint-plugin-node checks for basic Node app issues.

eslint-plugin-mocha has basic Mocha code linting.

eslint-plugin-node-security checks for security issues.


We can log, test and lint to improve our Node apps.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

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