Categories
Node.js Best Practices

Node.js Best Practices — Production and Security

Spread the love

Node.js is a popular runtime to write apps for. These apps are often production quality apps that are used by many people. To make maintaining them easier, we’ve to set some guidelines for people to follow.

In this article, we’ll look at deployment and security-related best practices for Node apps.

Set NODE_ENV=production

We should set NODE_ENV to production for production environments and development for development. Setting NODE_ENV to production makes production-related optimizations activate.

Omitting this single property may degrade performance significantly. For instance, using Express without NODE_ENV set to production makes it slower by a factor of 3.

In Express, if NODE_ENV is production , it’ll cache view templates and CSS generated from CSS extensions, and it’ll also generate less verbose error messages to keep them from being exposed to the public.

Design Automated, Atomic and Zero-Downtime Deployments

Deployments need to be automated so that we don’t have to worry about it once the automated deployment pipeline is set up. This way, we don’t have to worry about running into issues caused by human error. Manual deployments are also much slower than automatic.

Even better, we can create automated sandbox environments with Docker so that each app runs in its own environment. This solves lots of issues with conflicting runtimes, packages, and environment variables.

Otherwise, we have to deploy manually every time, which may cause issues. Also, we have to watch it to make sure that it’s done, and so people are less likely to deploy apps since it’s such a painful and error-prone process.

Use an LTS Release of Node.js

Long Term Service (LTS) releases of Node are supported for much longer than non-LTS Node versions. They constantly receive critical bug fixes, security updates, and performance improvements. Non-LTS releases are only supported for a few months after its releases with updates.

Therefore, to avoid having to upgrade Node all the time, we should use the LTS versions.

Embrace Linter Security Rules

ESLint plugins like eslint-plugin-security checks for security issues with our code so that we can fix them as early as possible. This helps catch security weaknesses like eval , invoking a child process, or importing a module with user inputted string.

With it, we can check before our code is committed or pushed. With this, we also follow security best practices all the time by everyone in the team.

Limit Concurrent Requests Using a Middleware

Denial of Service (DOS) attacks are very popular and easy to do. Therefore, we should implement rate-limiting using a service like load balances, cloud firewalls, reverse proxy, or a package in our app.

To implement rate-limiting in our app endpoints, we can use packages like rate-limiter-flexible or express-rate-limit .

For instance, we can use express-rate-limit as follows to add a rate limit to all the endpoints in our Express app:

const express = require('express');
const bodyParser = require('body-parser');
const rateLimit = require("express-rate-limit");

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});

app.use(limiter);

app.get('/', (req, res, next) => {
  res.send('hello');
});

app.listen(3000, () => console.log('server started'));
module.exports = app;

As we can see, we only have to add a simple middleware to prevent denial of service attacks on our app. Therefore, we should do it from the beginning for apps of any size.

Extract Secrets from Configuration Files or Use Packages to Encrypt Them

Configuration files often have secret data that shouldn’t be seen by most people. Therefore, we should never store them in our source code. Instead, we have to make sure of secret-management systems like Vault, Docker secrets, or use environment variables.

We should have pre-commit or push hooks to prevent accidentally committing secrets to code. Source control can be mistakenly made public, which exposes the secrets to other people that shouldn’t see them.

For example, we can use the dotenv package to store environment variables for Node apps. To use it, we write:

const express = require('express');
const bodyParser = require('body-parser');
require('dotenv').config();

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.get('/', (req, res) => {
  res.send(process.env.DB_HOST);
});

app.listen(3000, () => console.log('server started'));

Given that our .env file has:

DB_HOST=foo

Once we ran:

require('dotenv').config();

Then process.env.DB_HOST returns 'foo' , so we’ll see foo displayed on the screen if we go to / .

Conclusion

We should make sure that NODE_ENV is set to production so that optimizations are applied for packages like Express.

Any deployment should be automated. Also, Node.js should be the LTS version. Also, we should check for security vulnerabilities in our code and also rate limit our API endpoints to prevent denial of service attacks.

Finally, we should keep secrets out of our code.

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 *