Categories
Node.js Best Practices

Node.js Best Practices — Versioning and Security

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 Semantic Versioning

We should use semantic version to version our app.

It’s conventional so that many people will understand it.

The version should have the major version, minor version, and bug fix version and separated by dots.

So the format is major.minor.bugfix

Secure Our Applications

We should secure user and customer data so that we can protect our app against any attacks.

Things that we should be aware of include security HTTP headers, brute force attacks, and more.

We should have some headers in our HTTP response.

They include Strict-Transport-Security which enforces HTTPS connections to the server.

X-Frame-Options provides clickjacking protection.

X-XSS-Protection enables cross-site scripting filter built into most recent web browsers.

X-Content-Type-Options prevents browsers from MIME-sniff a response from the declared content type.

Content-Security-Policy prevents a wide range of attacks like cross-site scripting and other cross-site injections.

We can enable all of them with the Helmet module:

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

const app = express();

app.use(helmet());

There’s also the Koa version, which is the koa-helmet module.

Also, we can use it in the Nginx seer to add the headers.

In nginx.conf , we can add:

add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'";

Sensitive Data on the Client Side

We should never expose API secrets and credentials in our source code since it’ll be readable by anyone.

We can check them in code reviews.

Brute Force Protection

Brute force protection should be added to our app so that we can prevent these attacks.

To do this, we add a rate-limiting library to limit the requests that can be made.

We can use the ratelimiter package to limit the number of times a function is called:

const limit = new Limiter({ id, db });

limit.get((err, limit) => {

});

We can write Express middleware with it.

For example, we can write:

const ratelimit = require('koa-ratelimit');
const redis = require('redis');
constkoa = require('koa');
constapp = koa();

const emailBasedRatelimit = ratelimit({
  db: redis.createClient(),
  duration: 100000,
  max: 10,
  id(context) {
    return context.body.email;
  }
});

const ipBasedRatelimit = ratelimit({
  db: redis.createClient(),
  duration: 100000,
  max: 10,
  id(context) {
    return context.ip;
  }
});

app.post('/login', ipBasedRatelimit, emailBasedRatelimit, handleLogin);

We check the ID and email with our rate limit middleware with the id method.

duration sets the duration.

db is the Redi connection instance.

max is the max number of requests.

Session Management

To secure our cookies, we set a few flags.

One of them is the secure flag. This tells the browser to only send the cookie if the request is being sent over HTTPS.

HttpOnly is used to help prevent attacks like cross-site scripting since it disallows cookies to be accessed with JavaScript.

The scope of the domain should also be changed.

The domain compares against the domain of the server to which the URL is being requested.

If the domain matches, then the path has to match.

Once the path is checked then the cookie will be sent with the request.

expires is the attribute used to set persistent cookies. They expire after the expiry date has passed.

Conclusion

We should use semantic version and take some steps to secure our app.

Categories
Node.js Best Practices

Node.js Best Practices — Scaling and Technology

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 npm Scripts

We can use NPM scripts to put our scripts for builds, tests, and starting the app.

This way, we can type less to do all those tasks as we put them all in one command.

We put our scripts in package.json to make our lives easier.

So we can put:

"scripts": {  
  "preinstall": "node preinstall.js",  
  "postintall": "node postintall.js",  
  "build": "webpack",  
  "postbuild": "node index.js",  
  "postversion": "npm publish"  
}

in our file so that we can run them with npm run preinstall , npm run postinstall , etc.

If we need to run multiple commands, we can use && .

Command-line tools like Webpack, Nodemon, etc. should be run as local dev dependencies to avoid conflicts.

Use Env Vars

We should make our app configurable so that we can run them in any environment.

We can set them in various places, including the command to start our app:

NODE_ENV=production MONGO_URL=mongo://localhost:27017 nodemon index.js

or in Nodemon’s own config, which is nodemon.json:

{  
  "env": {  
    "NODE_ENV": "production",  
    "MONGO_URL": "mongo://localhost:27017/accounts"  
  }  
}

Just remember not to check in any secrets for our app.

Event Loop

If we need to perform long-running tasks, then we need to queue them in the event loop.

There’re various ways to do this.

setImmediate and setTimeout both run in the next event loop cycle.

nextTick works on the same cycle.

Use Class Inheritance

The class syntax makes inheritance easier with the extends keyword.

It makes sure that we call the parent constructor and lets us inherit things without working with prototypes directly.

Even though the class syntax is syntactic sugar for prototypes, it makes our lives easier.

Name Things Appropriately

We should name things appropriately so that we don’t have to explain the meaning of them to people.

So instead of writing:

const foo = require('morgan')  
// ...  
app.use(foo('dev'))

We write:

const logger = require('morgan')  
// ...  
app.use(logger('dev'))

Using JavaScript?

We can use extensions to JavaScript like TypeScript to make our lives easier.

They often let us restrict data types in various ways that JavaScript can’t do.

TypeScript also provides other handy features like interfaces and type aliases to restrict the structure of our objects.

It also has type guards to infer types.

The TypeScript compiler can compile to JavaScript versions as early as ES3.

It should serve anyone’s needs.

Express Middleware

Express middleware lets us make our Express app modular.

Many Express add-ons are available as middleware.

They include things like body-parser for parsing request bodies and many more.

Routes are also middlewares.

They let us build our Express app easily.

Therefore, we should know how to create and use them.

Scale Up

We rely on async code in Node so that we run code that doesn’t block other parts from running.

It only has one thread so we can’t run anything else until that piece of code is done.

To use more than one core of a processor, we’ve to create a cluster.

PM2 is a simple process manager that lets us create clusters easily.

We run:

npm i -g pm2

to install it.

Then we run:

pm2 start server.js -i 4

to run server.js with 4 instances with each instance running on its own core.

There’s also pm2-docker for Dockerized apps.

We can put:

# ...  
  
RUN npm install pm2 -g  
  
CMD ["pm2-docker", "app.js"]

in our Dockerfile to run it.

Conclusion

We can scale up with process managers and some handy tricks would help us with developing easier.

Categories
Node.js Best Practices

Node.js Best Practices — Project 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.

Folder Structure

Our Node app should follow some standard folder structure.

For example, we can have something like:

src
│   app.js
└───api
└───config
└───jobs
└───loaders
└───models
└───services
└───subscribers
└───types

app.js is the app’s entry point.

api has the controllers for the endpoints.

config has the environment variables and configuration related stuff.

jobs have scheduled jobs.

loaders have the code that runs when the app starts.

models have the database models.

services has business logic.

subscribers have the event handlers for queues, etc.

types are type definitions for TypeScript projects.

3 Layer Architecture

The 3 layer architecture consists of the controllers, service layer, and the data access layer.

The controller is the interface to the client.

It takes requests and sends responses.

The service layer has the logic, which takes stuff from controllers and returns things to them.

The data access layer has the database logic which talks to the service layer.

We should never put business logic in controllers.

Separating them makes testing them easier when we write unit tests.

When we test controllers, we can just mock all the service entities.

Service Layer for Business Logic

The service layer is used for business logic.

It’s all about doing everything that controllers and database layers don’t do.

For example, we can write:

route.post('/',
  validators.userSignup,
  async (req, res, next) => {
    const userParams = req.body;
    const { user } = await UserService.Signup(userParams);
    return res.json(user);
  });

The UserService.Signup has the business logic.

The request and response are handled by the controller.

Pub/Sub Layer

The pub/sub layer is used for listening to events from external sources.

The pub part sends data to other modules.

Separating this logic makes sense since they create a cohesive layer.

Dependency Injection

Dependency injection lets us handle all the dependency initialization in one place.

For example, we can write:

export default class UserService {
  constructor(userModel, companyModel, employeeModel){
    this.userModel = userModel;
    this.companyModel = companyModel;
    this.employeeModel = employeeModel;
  }

  getUser(userId){
    const user = this.userModel.findById(userId);
    return user;
  }
}

We have the UserService class that has takes all the required dependencies in the constructor.

Then we use throughout the class.

We can use 3rd party solutions to make this easier.

The typedi library provides us with a dependency injection container to let us inject dependencies.

We can use dependency injection with Express by writing something like:

route.post('/',
  async (req, res, next) => {
    const userParams = req.body;
    const userServiceInstance = Container.get(UserService);
    const { user} = userServiceInstance.Signup(userParams);
    return res.json(user);
  });

We call the Container.get method from typedi to get the UserService so we can use it.

Cron Jobs and Recurring Task

Cron jobs and scheduled tasks should be in their own folder.

We can use the business logic from the service layer.

Also, we shouldn’t use setTimeout or another primitive way to delay the execution of code.

Instead, we should use a library to help us with this.

This way, we can control failed jobs and have feedback on ones that succeed.

Conclusion

We should create apps with a standard structure.

The folders cohesively organize different parts of our app.

Categories
Node.js Best Practices

Node.js Best Practices — Profile, Watch, and Requests

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 Node’s Built-in Profiler

Node comes with its own profiler to let us watch the performance of our app.

To use it, we just add the --prof option when we run our app:

node --prof app.js

Then we process our tick file that’s outputted from this by running:

node --prof-process isolate-0x???????-v8.log

And then we can read the tick file processing result.

There’s a [Summary] section with the data that came from the profiling.

It consists of what kind of code is run like library and nonlibrary code.

It also shows what language the code is written in.

Use a Code Change Watcher to Automatically Restart Your Node App

To make developing Node apps easier, we should use a code change watcher.

With them, the app is restarted when we change our code files.

There are several packages that do that.

One of them is Nodemon.

We can install it by running:

npm install -g nodemon

Then we use nodemon instead of node to run our app.

Another package to do this is Forever.

We can install it by running:

npm install -g forever

Then we start our app by running:

forever start app.js

It has some options like appending logs to a file instead of stdout, saving the process ID to a file, etc.

Node-supervisor is another package we can use.

To install it, we run:

npm install -g supervisor

It has several options to change its behavior like not restarting on error, etc.

Properly Use Logging in Node.js

console.log have a few problems.

Once we built our app, we’ve to remove them all to avoid polluting our log files.

Also, we have no options for filtering them.

A better alternative is to use the debug module for logging.

To use it, we require it by writing:

const debug = require('debug')('my-app');

where 'my-app' is our app name.

Then we can log things with the debug function:

debug("hello world", someVar, someOtherVar);

We can pass in whatever we want to the function to log them.

To turn on debug messages when we run our app, we run:

DEBUG=my-app node app.js

The value for DEBUG should match the name we passed in when we required it.

The name of the app instance can also be namespaced:

const debug = require("debug")("my-app:startup");

This lets us distinguish each level of debugging precisely.

So we can run:

DEBUG=my-app:startup node app.js

to log startup messages and:

DEBUG=my-app:* node app.js

to log all messages in the namespace.

Properly Use Promises

We should avoid creating new promise each time a request is made.

Instead, we can put it in a function and reuse it:

const axios = require('axios');

const makeRequest = (options) => {
  return axios(options)
};

const getRequest = (url) => {
  const options = {
    method: "GET",
    url
  };
  return makeRequest(options);
};

const getProfile = (profileId) => {
  return getRequest(`/profile/${profileId}`);
};

We make a request Axios, which returns a promise.

This way, we can reuse the piece of generic request code for all requests.

Conclusion

We can profile our app with the built-in profiler.

Also, we can use packages to restart our app when code changes.

The debug module is good for logging debug messages.

And we can create one common function that makes all HTTP requests.

Categories
Node.js Best Practices

Node.js Best Practices — Process Managers

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 a Process Manager

We should use a process manager to keep our Node app running even when it crashes.

A crash will bring down a Node app.

It’ll be offline until we restart it.

Therefore, we can’t just run it with node app.js or something similar.

A process manager helps our app maintain high availability.

It lets us gain insights into runtime performance and resource consumption.

Settings can be modified dynamically to improve performance.

We can also control clustering with StrongLoop PM and PM2.

StrongLoop PM has features that target production deployment.

We can build and package apps locally and deploy it securely to our production system.

Also, it automatically restarts our app if it crashes.

It also lets us manage clusters remotely.

CPU profiles and heap snapshots let us check for memory leaks and CPU usage.

And we can scale to multiple hosts with integrated control for the Nginx load balancer.

Use an Init System

An init system provides more reliability in that it ensures the apps start when the server restarts.

They can go down for many reasons so we should make sure our app starts again when it starts.

We can run our app in a process manager and install the process manager as a service with the init system.

The process manager would restart our app when the app crashes.

The init system will restart the process manager when the OS crashes.

We can also run our app directly in the init system.

This is simpler but we don’t get the additional privileges of using a process manager.

The 2 main init systems are systemd and Upstart.

Using Node’s Cluster Module

Node’s cluster module lets us create multiple instances of one app.

It enables a master process to spawn worker processes and distribute incoming connections among workers.

We can use StrongLoop PM to create a cluster without modifying application code.

StrongLoop PM automatically runs in a cluster with the number of workers equal to the number of CPU cores in a system.

We can manually change the number of worker processes in a cluster using the slc program without stopping the app.

For example, we can run an app with the given cluster size by writing:

slc ctl -C http://prod.example.com:8888 set-size my-app 8

We set the cluster size to 8 with the number 8 at the end.

PM2 also lets us create clusters without modifying our app’s code.

We must ensure that are app is stateless.

This means that no local data should be stored in the process like sessions, WebSocket connections, etc.

We can then enable cluster mode by running:

$ pm2 start app.js -i 4  
$ pm2 start app.js -i max

We run a cluster with 4 worker processes with the 4 at the end.

Or we can use max to all the CPUs and start that many worker processes.

To add more workers we can use the plus sign:

$ pm2 scale app +3  
$ pm2 scale app 2

We use scale to change the number of workers.

Conclusion

We can use process managers to create clusters, restart our app, and monitor hardware usage.