Categories
Node.js Best Practices

Node.js Best Practices — Config and Errors

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 Environment Aware, Secure and Hierarchical Config

We should make our app configurable.

To do that, we can read the settings from environment variables.

For instance, we can use the dotenv library to read environment variables from a .env file and other sources.

This will make our app run properly anywhere.

Use Async-Await or Promises for Async Error Handling

Async code should be used for any long-running operation.

The worst way to chain async code is to have multiple levels of nested async callbacks.

They’re hard to read and debug.

The best way is to use promises and chain them.

To make them even shorter, we can use async and await.

So either, we write:

return functionA()
  .then(functionB)
  .then(functionC)
  .then(functionD)
  .catch((err) => console.error(err))
  .then(alwaysRunThisFunction)

or:

async function executeAsyncTask () {
  try {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    const valueC = await functionC(valueB);
    return await functionD(valueC);
  }
  catch (err) {
    console.error(err);
  }
  finally {
    await alwaysRunThisFunction();
  }
}

They do the same thing.

The callbacks all return promises so we can chain them.

Use Only the Built-in Error Object

When we throw errors, we should use the built-in error type.

For instance, we should write:

new Error('whoops!')

instead of:

throw ('whoops!');

The Error instance has information like stack traces that aren’t available anywhere else.

Distinguish Operational vs Programmer Errors

We should distinguish operational errors like API errors where they can be handled properly.

Programmer errors are errors like undefined errors that we didn’t take into account.

Programmer errors should be fixed so that our app will run smoother.

Operational errors should be handled by our code.

Handle Errors Centrally, not Within an Express Middleware

Errors should be handled centrally rather than with an Express middleware.

Otherwise, we’ll get duplicated code in multiple places to handle the same error.

For example, instead of writing:

app.use((err, req, res, next) => {
  logger.logError(err);
  if (err.severity == errors.high) {
    mailer.sendMail(configuration.adminMail, 'error occured', err);
  }
  next(err);
});

We write:

module.exports.handler = new ErrorHandler();

function ErrorHandler() {
  this.handleError = async (err) => {
    await logger.logError(err);
    await sendEmail;
    await saveInOpsQueueIfCritical;
    await determineIfOperationalError;
  };
}

We have a central error handler we can import into other modules.

Document API Errors Using Swagger or GraphQL

We should document the error s that may occur with our REST APIs with Swagger or GraphQL.

This way, we can use the API and handle the errors that are documented.

If we handle the errors that are encountered, then it won’t crash.

Exit the Process Gracefully

If an unknown error is encountered, we should exit the process gracefully with a process manager.

This can be done with a process manager with Forever or PM2.

These tools can restart our app if any errors occur.

Conclusion

We should document any errors that APIs can throw so they can be handled.

Async code and making our app configurable are also good ideas.

Categories
Node.js Best Practices

Node.js Best Practices — Security and Structure

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 Helmet if we’re Writing a Web App

We should use Helmet if we’re writing a web app.

It does a few things, including:

  • add XSS protection
  • prevent clickjacking with X-Frame-Options
  • enforcing all connection to be HTTPS
  • setting Context-Security-Policy header
  • disable X-Powered-By response header so attackers can’t narrow down the libraries we’re using to write the app.

Helmet will set sensible defaults for all those options.

We can install it by running:

npm install helmet

In our Express app, we can use the middleware by writing:

const helmet = require('helmet');  
app.use(helmet());

Monitor our Applications

If we’re running our app, then we need to monitor it.

Users aren’t happy if our app goes down and there’s no quick resolution.

Therefore, we need to monitor our app and alert everyone so that we can get it running again quickly.

For instance, KeyMetrics.io integrates with PM2 to check for the app’s health.

A dashboard is also provided to show us when it’s up or not.

Latency and events can be checked.

Test our Code

We can test our code with automated tests so that we have peace of mind when we change our code.

They run quickly and automatically so that we don’t have to check every part of our app ourselves.

We should add tests when we fix bugs and run our tests regularly.

There’re a few ways to run tests.

We can use Mocha, Chai, Jest, or Jasmine to run them.

They’re all popular and provide equivalent functionality.

To create tests that make requests, we can use Supertest to make the requests and check the results.

Structure Solution by Components

We should structure our project by components.

This way, we can find them later.

It’s easy to get lost if a project has no structure.

We should divide our code into modules.

Layer Our Components and Keep Express within its Boundaries

We should only use Express for the controller portion of the app.

Business logic should be in their own modules to organize our app better.

This way, each module, and function does its own thing.

Mixing different parts together makes them hard to test and maintain.

Wrap Common Utilities as NPM Packages

If we have common things that are used in multiple projects, we should put them in their own package.

Common functionality like logging, encryption, etc. should be in their own package.

This way, we only have to change one package to update the functionality.

Separate Express ‘app’ and ‘server’

Our Express app shouldn’t be one big file.

The entry point should be separate from the rest of the app.

One big file makes everything slower.

The API should be in app.js and the networking code should be in www .

The API declaration can also be split into component if it’s big.

Conclusion

We can organize our app better.

Also, we can take steps to improve security and monitor our app.

Categories
Node.js Best Practices

Node.js Best Practices — Error Handling

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.

Handle Errors

If there’s any chance that an error may occur with a piece of code, we should handle the error.

For example, if we have a change of promises, we should call catch with an error handler callback passed into it.

We can write:

doSomething()
  .then(doNextStage)
  .then(doMoreThins)
  .then(updateInterestedParties)
  .then(cleanUp)
  .catch(errorHandler);

We have the catch method with the errorHandler callback function passed into it.

The callback will be called when any of the promises in the chain throws an error.

Ensure our App Automatically Restarts

We should ensure that our app automatically restarts.

This way, we won’t have to do this ourselves when our app encounters an unhandled error.

We need a process manager like Nodemon, forever, or PM2 to do this.

The last 2 are for production usage.

Nodemon is used for development more often.

For instance, we can install PM2 globally with:

npm install pm2 -g

Then we can launch our Node app with:

pm2 start app.js

Cluster our App to Improve Performance and Reliability

We can course our app so that we have more than one instance of it.

This way, we can distribute the work across all cores.

We need this since Node apps run in a single process.

PM2 lets us do this easily by running:

pm2 start app.js -i max

This lets us run multiple processes equal to the number of cores present in the server.

The processes don’t share memory or resources.

They’ll all have their own connection to the database, for instance.

Therefore, we should use something like Redis to keep session state across all instances.

Require All Dependencies Upfront

It’s a good idea to have our require calls all at the top so we won’t have to look for them in various parts of our code.

Also, we can spot any issues with them earlier.

For example, we can write:

const datastore = require("db")(someConfig);

app.get("/my-service", (request, response) => {
  db.get(req.query.someKey)
  // ...
});

We call require at the top so that we run them all earlier.

If there’re any errors with them, they’ll be thrown at the beginning.

Also, require is synchronous, so if it takes a long time to run, it’ll hold up our app.

If there any problems like these, we’ll find them early if we require them early.

Use a Logging Library to Increase Errors Visibility

Errors can happen in any environment.

And we want them to be visible so that we can fix them.

console.log is very limiting in a production setting.

A better logging library can give us more control over how to log.

We can have various types of logging or save the data somewhere.

Some good libraries include Loggly and Winston. Winston is the base package for basic logging.

And Node-Loggly uses Winston for logging.

This way, we’ll see the errors in one central place that’s searchable.

Conclusion

Error handling can be made easy with a few changes to our code.

We should catch errors and log them so we can fix them is needed.

Categories
Node.js Best Practices

Node.js Best Practices — Project and Async

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.

Start All Projects with npm init

We should start will project with npm init so that we get the package.json file in our app project.

This will let us add our project’s metadata like the project name, version, and the required packages.

Setup .npmrc

We should add .npmrc to add the URL for the Node packages repositories that we’ve to use outside the regular NPM repository.

Also, we can make the version of the package the same for everyone.

To make everyone use the same version, we can write.

npm config set save=true
npm config set save-exact=true

We make npm install also install the same version.

package-lock.json also locks the repositories to the same version.

Add Scripts to Our package.json

package.json can hold project-related scripts.

They’re all in the scripts folder.

This way, we can just have one command to do everything to start the project.

We can add a start script to our project with npm start .

For example, we can write:

"scripts": {
  "start": "node app.js"
}

Then just run npm start to run our app instead of node app.js .

Also, we can add more commands:

"scripts": {
  "postinstall": "bower install && grunt build",
  "start": "node app.js",
  "test": "node ./node_modules/jasmine/bin/jasmine.js"
}

postinstall runs after the packages in package.json are installed.

npm test runs the test script which runs the tests.

This lets us run tests without typing in multiple commands ourselves.

We can also add custom scripts into package.json .

We can run them with npm run script {name} where name is the key of the script.

Use Environment Variables

We should set environment variables for things that run in different environments.

For example, we can use the dotenv library to read them from an .env file or environment variables so we can use them in our app.

They can be accessed with process.env in our Node app.

Use a Style Guide

If we adopt styles in a style guide, then we write our code in a consistent style.

Having consistent code makes it easier to understand.

We can make the checks automatic with linters like ESLint.

It contains rules for Node apps built-in.

Other style guides include:

Embrace async

We got to use async code with Node apps.

This way, they won’t hold up the whole app while waiting for something to complete.

To make our lives easier, we can use promises to write our async code.

To make them shorter, we can use async and await.

We can convert some Node async callbacks to promises with the util module.

Some modules like fs also have versions of its methods that return promises.

Conclusion

We should take full advantage of the features provided by package.json and the npm program.

Also, we should use async wherever we can.

A consistent coding style is also good.

Categories
Node.js Best Practices

Node.js Best Practices — Security and Config

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.

Block Cross-Site Request Forgeries

We should block cross-site request forgeries.

This is an attacker where attackers can attempt to put data into an application via their own site.

The attacker creates a request with a form or other input that creates requests against an app.

To mitigate cross-site request forgeries, we can use the csurf package.

For instance, we can write:

const express = require(‘express’);
const csrf = require('csurf');

const app = express();

app.use(csrf());

app.use(function(req, res, next){
 res.locals.csrftoken = req.csrfToken();
 next();
});

We use the csrf middleware so we can get the CSRF token with the csrfToken method.

Then we can use it in our template with:

<input type="hidden" name="<i>csrf" value={{csrftoken}} />

Don’t Use Evil Regular Expressions

Evil regex includes grouping with partitions, repetition, and alternation with overlapping.

These patterns can take exponential time to computed when applied to certain non-matching inputs.

Examples of these patterns include:

  • (a+)+
  • ([a-zA-Z]+)*
  • (a|aa)+

If check against some input like aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, then we would hang our app.

It can take seconds or minutes to complete the pattern check.

We can audit evil regexes with:

Add Rate Limiting

Rate limiting will protect us from DOS attacks.

We don’t want attackers to bombard our app with lots of requests and let them all go through.

To limit the number of requests to our app from one IP address, we can use the express-limiter package.

For example, we can write:

const express = require('express');
const redisClient = require('redis').createClient();

const app = express();

const limiter = require('express-limiter')(app, redisClient);

limiter({
  lookup: ['connection.remoteAddress'],
  total: 100,
  expire: 1000 * 60 * 60
})

We use the limiter middleware to limit requests up to 100 per hour per IP address.

total is the number of requests.

expire is when the limit is reset.

Docker Compose

We can create our Docker compose config to install Nginx with our app together.

For instance, we can write:

web:
  build: ./app
  volumes:
    - "./app:/src/app"
  ports:
    - "3030:3000"
  command: pm2-docker app/server.js
  nginx:
    restart: always
    build: ./nginx/
  ports:
    - "80:80"
  volumes:
    - /www/public
  volumes_from:
    - web
  links:
    - web:web

We install Nginx and our app all in one go with our Docker compose file.

Keep a Clear Separation Between the Business Logic and the API Routes

We should keep a clear separation between the business logic and the API routes.

We definitely shouldn’t have our logic in our API routes since a route can do many things.

We need a service layer with all the business logic so that we can work with and test them separately.

Also, we can use everything easier.

Use a config Folder for Configuration Files

Configuration files should be in a config folder so that we can add all the configuration into one place.

It’s easy to find and change this way.

Conclusion

Evil regex and folder structure should be taken into consideration when we create our Node app.