Skip to content

Node.js Best Practices

Node.js is a popular runtime environment for building server-side applications using JavaScript. In this post, I will share some best practices for writing Node.js applications.

Table of contents

Open Table of contents

General

Use import and export instead of require

Node.js has supported ES modules (ECMAScript modules) since version 12. You can use import and export statements to load modules instead of require and module.exports.

// Importing modules
import fs from 'fs';
import path from 'path';

// Exporting modules
export function sum(a, b) {
  return a + b;
}

Gracefully Shutting down the application

Use the process object to gracefully shut down your Node.js application when it receives a termination signal.

You can listen for the SIGINT and SIGTERM signals and perform cleanup tasks before exiting.

process.on('SIGINT', () => {
  console.log('Received SIGINT signal');
  // Perform cleanup tasks
  process.exit(0);
});

process.on('SIGTERM', () => {
  console.log('Received SIGTERM signal');
  // Perform cleanup tasks
  process.exit(0);
});

Always use try/catch for asynchronous code

Use try/catch blocks to handle errors in asynchronous code. This allows you to catch and handle exceptions that occur during the execution of your code.

try {
  const data = await fetchData();
  console.log(data);
} catch (error) {
  console.error('Error fetching data:', error);
}

Use promises and async/await for asynchronous operations

Node.js provides support for asynchronous programming using promises and async/await. You can use async functions and the await keyword to write asynchronous code that is easier to read and maintain.

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

Use dotenv for environment variables

The dotenv module allows you to load environment variables from a .env file into process.env.

This makes it easy to manage configuration settings for your Node.js application.

require('dotenv').config();

const PORT = process.env.PORT || 3000;
const DB_URL = process.env.DB_URL;

Use a linter and formatter

Using a linter and code formatter can help you catch errors and enforce coding standards in your Node.js codebase. Popular tools like ESLint and Prettier can be used to ensure consistent code style across your project.

Use a logging library

Logging is an essential part of any application for monitoring and debugging. You can use a logging library like winston or pino to log messages to the console, files, or other destinations.

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports
});

logger.info('Hello, world!');

Use a testing framework

Writing tests for your Node.js application is crucial for ensuring its reliability and maintainability. You can use testing frameworks like Jest or Mocha to write unit tests, integration tests, and end-to-end tests for your code.

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Security

Never Run Node.js with Root Privileges

Running Node.js with root privileges can be dangerous as it gives the application full access to the system.

Always run Node.js applications with a dedicated user with the permissions required to run the application. This helps to prevent security vulnerabilities.

Keep NPM Packages Updated

Regularly update the NPM packages used in your Node.js application to ensure that you have the latest features, bug fixes, and security patches.

Use the npm outdated command to check for outdated packages and npm update to update them.

npm outdated
npm update

Use npm audit to check for security vulnerabilities in your dependencies and take appropriate action to fix them.

npm audit
npm audit fix

Set the Security HTTP Headers

Set security HTTP headers in your Node.js application to protect it from common security vulnerabilities like cross-site scripting (XSS), clickjacking, and MIME sniffing attacks.

You can use the helmet middleware to set security headers in your Express.js application.


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

const app = express();

app.use(helmet());

The helmet() middleware automatically removes unsafe headers and adds new ones, including X-XSS-Protection, X-Content-Type-Options, Strict-Transport-Security, and X-Frame-Options. These enforce best practices and help protect your application from common attacks.

Validate User Input

Always validate user input to prevent common security vulnerabilities like SQL injection, cross-site scripting (XSS), and command injection attacks.

Use a validation library like express-validator to validate user input in your Node.js application.

const { body, validationResult } = require('express-validator');

app.post('/login', 
  body('username').isEmail(),
  body('password').isLength({ min: 5 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Continue with the login logic
  }
);

Use HTTPS

Always use HTTPS to secure communication between the client and server in your Node.js application.

You can use the https module in Node.js to create an HTTPS server and configure it with SSL/TLS certificates.

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem')
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('Hello, world!');
}).listen(443);

Limit Request Size

Limit the size of incoming requests to prevent denial-of-service (DoS) attacks and protect your Node.js application from memory exhaustion.

You can use the body-parser library to limit the size of incoming JSON requests.

const bodyParser = require('body-parser');

app.use(bodyParser.json({ limit: '1mb' }));

References