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.

Categories
Node.js Best Practices

Node.js Best Practices — REST and Test

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 HTTP Methods & API Routes

We should follow RESTful conventions when we create our endpoints.

We should use nouns as identifiers.

For example, we have routes like:

  • POST /article or PUT /article/:id to create a new article
  • GET /article to retrieve a list of articles
  • GET /article/:id to retrieve an article
  • PATCH /article/:id to modify an existing article
  • DELETE /article/:id to remove an article

Use HTTP Status Codes Correctly

Status codes should correctly tell the status of our response.

We can have the following:

  • 2xx, if everything is fine.
  • 3xx, if the resource has moved
  • 4xx, if the request can’t be fulfilled because of a client error
  • 5xx, if something went wrong on the API side.

Client-side errors are things like invalid input or unauthorized credentials.

Server-side errors are things like exceptions thrown on the server-side for whatever reasons.

We can respond with status codes in Express with res.status .

For example, we can write:

res.status(500).send({ error: 'an error occurred' })

We respond with the 500 status code with a message.

Use HTTP headers to Send Metadata

HTTP headers let us send metadata with requests and responses.

They can include information like pagination, rate-limiting, or authentication.

We can add custom headers by prefixing the keys with X- .

For instance, we can send a CSRF token with the X-Csrf-Token request header.

HTTP doesn’t define any size limit on headers.

However, Node imposes an 80KB limit as the max heart size.

The Right Framework for Our Node.js REST API

We should pick the right framework for our REST API.

There’s Koa, Express, Hapi, Resify, Nest.js, and more.

We can use the first 4 to build simple rest services.

If we need a more complete solution, we can use Nest.js.

It has things like ORM and testing built-in.

Black-Box Test Our Node.js REST APIs

To test our Node REST APIs, we can make requests to our API and check the results.

We can use a specialized HTTP client like Supertest to test our API.

For example, to test getting an article with it, we can write:

const request = require('supertest')

describe('GET /user/:id', () => {
  it('returns a user', () => {
    return request(app)
      .get('/article')
      .set('Accept', 'application/json')
      .expect(200, {
        id: '1',
        title: 'title',
        content: 'something'
      }, done)
  })
})

We make the HTTP request to the article endpoint.

And we call set to set some request headers.

Then we call expect to check the status code and response body respectively.

The data would be populated in a database that’s only used when running unit tests.

They would be reset after every test.

This ensures that we have clean data to test with.

In addition to black-box tests, we should also do unit tests for other parts like the services.

Conclusion

We should follow RESTful conventions for our APIs.

Also, testing is important for any app.

Categories
Node.js Best Practices

Node.js Best Practices — Performance and Uptime

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.

Monitoring

We must monitor our app to make sure that it’s in a healthy state.

The number of restarts, resource usage, and more indicates how our app is doing.

We use these tools to get a good idea of what’s happening in production

Delegate Anything Possible to a Reverse Proxy

Anything that can be done by a reverse proxy should be done by it.

So we can use one like Nginx to do things like SSL, gzipping, etc.

Otherwise, our app would be busy dealing with network tasks rather than dealing with the app’s core tasks.

Express has middleware for all these tasks, but it’s better to offload them to a reverse proxy because Node’s single thread model would keep our app busy just doing networking tasks.

Make Use of SSL

SSL is a given since we don’t want attackers to snoop the communication channels between our clients and the server.

We should use a reverse proxy for this so we can delegate this to it rather than using our app for SSL.

Smart Logging

We can use a logging platform to make logging and searching the logs easier.

Otherwise, everything our app is doing is a black box.

Then if troubles arise, we’ll have problems fixing issues since we don’t know what’s going on.

Lock Dependencies

Dependencies should be locked so that new version won’t be installed without changing the version explicitly.

The NPM config in each environment should have the exact version and not the latest version of every package.

We can use npm shrinkwrap for finer controls of the dependencies.

They’re locked by default by NPM so we don’t have to worry about this.

Ensure Error Management Best Practices are Met

Errors should be handled so that we have a stable app.

Error handling is different for synchronous and async code.

We got to understand their differences.

Promises call catch to catch errors.

Synchronous code uses the catch block to catch errors.

We don’t want our app to crash when it does basic operations like parsing invalid JSON, using undefined variables, etc.

Guard Process Uptime Using the Right Tool

Node processes must be guarded against failures.

This means that they’ve to be restarted automatically when they crash.

A process manager like PM2 would do this for us and more.

It also provides us with cluster management features which we get without modifying our app’s code.

There’re also other tools like Forever that does the same thing.

Use All CPU Cores

We should use all CPU cores to run our Node app.

Node.js can only run on one CPU core without clustering.

This means by default, it’ll just leave the other cores idle.

We can do that with Docker or deployment scripts based on the Linux init system to replicate the process.

Conclusion

There’re many things we can do to maximize the performance of our Node app.

We can improve logging and monitoring to make troubleshooting easier.