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.

Categories
Node.js Best Practices

Node.js Best Practices — Helmet and Cookie

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.

Add Helmet to Set Sane Defaults

Some default settings for Express apps aren’t very secure.

Therefore, the Helmet middleware is available to set some saner defaults.

To use it, we write:

const express = require('express');
const helmet = require('helmet');

const app = express();

app.use(helmet());

when we create our Express app.

It does several things to improve the security of our app.

It enables the Content-Security-Policy HTTP header.

This defines the trusted origins of the contents like scripts, images, etc that are allowed to load on our web page.

DNS prefetching is good fir speeding up load times.

However, disabling prefetching will limit potential data leakage about the types of external services that are used.

It can also reduce traffic and costs associated with DNS query lookups.

The X-Frame-Options HTTP header is also enabled.

This blocks clickjacking attempts by disabling options for the webpage rendered on another site.

The X-Powered-By HTTP header is also hidden.

This way, attackers can’t identify what we’re using to create our app.

Public key pinning headers are also enabled.

This prevents man in the middle attacks that use forged certificates.

Strict-Transport-Security header is also enabled.

This forces subsequent connects to the server to use HTTPS once a client is connected with HTTPS initially.

It also enables the Cache-Control, Pragma, Expires, and Surrogate-Control with defaults that block clients from caching old versions of site resources.

The X-Content-Type-Options HTTP header stops clients from sniffing the MIME type of a response outside the content-type that’s declared.

Referred HTTP header in our app’s response header can also be controlled to include certain pieces of information.

The X-XSS-Protection HTTP header that prevents some XSS attacks in browsers.

Tighten Session Cookies

We should tighten sessions cookies that aren’t highly secure.

We can set various settings with the express-session package.

The secret property is a secret string for the cookie to be salted with.

key is the name of the cookie.

httpOnly flags cookies to be accessible by the issuing web server only.

secure should be set to true , which requires SSL/TLS.

This forces cookies to be only used with HTTPS requests.

domain indicates the domain that the cookie can be accessed from.

path has the path that the cookie is accepted within the app’s domain.

expires has the expiration date of the cookie is set.

This defaults to last for a session.

To use these options, we can write:

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'secret',
  key: 'someKey',
  cookie: {
    httpOnly: true,
    secure: true,
    domain: 'example.com',
    path: '/foo/bar',
    expires: new Date( Date.now() + 60 * 60 * 1000 )
  }
}));

We can set all these options to create and send our cookie.

Conclusion

The Express Helmet and express-session packages are very useful for securing our Express app.