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.

Categories
Node.js Best Practices

Node.js Best Practices — Modern Features

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 ES2015+

E2015 and later features have been supported by Node since v4.

This means that we can use those features in very early versions.

There’s no excuse not to use them now.

We don’t need Babel to use the latest features.

Most of the stage 4 features are available.

If we aren’t sure if our version of Node supports the feature we want, we can check node.green to be sure.

Use Promises

Promises let us write async code in a clean way.

They make our lives a lot easier.

Instead of writng:

fs.readFile('./foo.json', 'utf-8', (err, data) => {
  if (err) {
    return console.log(err)
  }

try { JSON.parse(data) } catch (ex) { return console.log(ex) } console.log(data.name) })


We write:

import * as Promise from "bluebird"; const fs = Promise.promisifyAll(require("fs"));

fs.readFileAsync('./foo.json')
.then(JSON.parse)
.then((data) => {
  console.log(data.name)
})
.catch((e) => {
  console.error('error reading file', e)
})
```

It’s much cleaner since there’s less nesting with multiple promises.

We used Bluebird to promise the whole fs module so we can use the promise versions of the provided methods.

### Use the JavaScript Standard Style

To make everyone's lives easier, we can use standard styles for JavaScript code.

This way, we don’t have to fight about formatting.

The [JavaScript Standard Style](https://github.com/feross/standard) is a useful style guide that covers most JavaScript syntax with its own rules.

Now we don’t have to make decisions about `.eslintrc` , `.jshintrc` and other linting config files.

We just use the ones provided by them.

### Use Docker

Docker makes everything easy.

It lets us run our app in isolation.

They’re lightweight.

There’re no manual steps needed for deployments.

The deployments are immutable.

And we can easily mirror production environments locally with it.

### Monitor Our Applications

If our apps are used by users, we got to make sure that they’re up.

The only way that we know is to monitor them.

We can do that with tools like [Prometheus](https://prometheus.io/).

It’ll alert us of any issues.

Also, it’ll show us the CPU and memory usage of our app.

Distributed tracking and error searching can also be done.

Performance monitoring is also built-in.

We can also use it to check for security vulnerabilities in the NPM packages we use.

### Use Messaging for Background Processes

If we have background processes, we should send messages between them so that we can retain when one end is down.

We can use some message queuing solutions for this. Examples include:

*   [RabbitMQ](https://www.rabbitmq.com/)
*   [Kafka](https://kafka.apache.org/)
*   [NSQ](http://nsq.io/)
*   [AWS SQS](https://aws.amazon.com/sqs/)

### Use the Latest LTS Node Version

The LTS Node version is supported longer than the non-LTS versions.

Support includes security patches and other bug fixes.

Therefore, we would want to use them.

To switch easily, we can use `nvm` .

Once we install ti, we can run:

```
nvm install 12.16.1
nvm use 12.16.1
```

to install and use version 12.16.1 respectively.

We can also specify the Node version in the Dockerfile.

### Conclusion

We can make our lives easier with some modern technologies like using ES2015+ and Dockerizing our app.

Categories
Node.js Best Practices

Node.js Best Practices — Microservices

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.

Microservices

Microservices architecture is a style that structure apps with a collection of services.

They’re very maintainable and testing.

Also, they’re loosely coupled so they can mostly work on their own.

They’re also independently deployable.

And each service is organized around business requirements.

This architecture lets us do continuous delivery and deployment of large systems.

We can also improve its technology stack in a piecemeal fashion.

If we’re creating a simple app, we probably don’t need microservices.

However, as our system grows, we don’t want to create one complex app that’s hard to maintain and scale.

It makes more sense to have them in individual modules.

Microservices are useful for replacing monolithic apps that are common until container solutions like Docker are commonplace.

We can divide our system into microservices with domains driven design.

Each domain is divided into bounded contexts which are mutually exclusive.

Each context correlates to a microservice.

Our goal is to create a cohesive and loosely coupled domain model.

We can identify the microservices we need by analyzing our domains, defining the bounded contexts, and define the entities and services.

Node.js Services

Now that we divided our system into microservices, each microservice can have their own pattern to organize their code.

Our app would be an MVC app which makes it easier to handle the model definition and interaction with the rest of the app.

We divide these entities into their own folders.

An MVC app has controllers to handle requests and responses.

It has no business logic.

Services have business logic. They’re passed to the controller.

Controllers can talk to many services.

Repository interacts with the models that are in the model folder.

These are used to query the database and won’t have business logic.

Models have the model definition and associations

Utilities have helper functions that are used by our app.

Tests have test cases. They test against controller methods to ensure max code coverage.

Cluster Modules

To maximize the use of CPU cores in our server, we should create clusters so that we can use more than one CPU core to run our app.

This is useful if we run our app outside Docker.

If we run our app in Docker, then we have one process per container so we don’t need to create clusters with apps in Docker.

Control Flow in Node.js

We should use promises to run async code in our app.

Any potentially long-running process should be written as promises.

This way, they won’t hold up the rest of our app from running.

Promises are native to ES6+, so we don’t have to add anything.

We can also convert some module methods to promises.

The fs module methods can be converted to promises.

Loops

We can run loops step by step in order.

We can run loops with a delay with for-await-of.

Things that aren’t dependent on each other can also be run in parallel.

For example Promise.all can be used to run multiple unrelated promises in parallel.

Conclusion

Microservices architecture results in coherent and loosely-coupled services that are part of a larger system.

This makes maintenance easier and we can improve faster.

Categories
Node.js Best Practices

Node.js Best Practices — Malicious Commands

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.

Setting Cookies

We can set cookies with Express.

We can use the cookie-session to send the cookies.

For example, we can write:

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

const app = express();

app.use(cookieSession({
  name: 'session',
  keys: [
    process.env.COOKIE_KEY1,
    process.env.COOKIE_KEY2
  ]
}));

app.use((req, res, next) => {
  const n = req.session.views || 0;
  req.session.views = n++;
  res.end(n);
});

app.listen(3000);

We use the cookie-session package to set the cookies.

CSRF

Cross-site requests forgery is an attack where a user does unwanted actions in the app that they’re logged in as.

These attacks target state-changing requests since they can’t see the response of the forged request.

To protect us from CSRF attacks, we can use the csrf module.

And in Express, we can use the csurf module.

For example, we can write:

const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const bodyParser = require('body-parser');
const express = require('express');

const csrfProtection = csrf({ cookie: true });
const parseForm = bodyParser.urlencoded({ extended: false });

const app = express();

app.use(cookieParser());

app.get('/form', csrfProtection, (req, res) => {
  res.render('send', { csrfToken: req.csrfToken() });
});

app.post('/process', parseForm, csrfProtection, (req, res) => {
  res.send('submitted');
});

Then we can add the form in our template:

<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="{{csrfToken}}">

  Name: <input type="text" name="name">
  <button type="submit">Submit</button>
</form>

We add a hidden input with the csrfToken in the form so that we can only submit the form when a CSRF token is present.

Data Validation

We should validate our data so that it prevents cross-site scripting.

Cross-site scripting occurs when attackers object HacaSruot code into HTML with specialty crafted links.

Stored cross-site scripting occurs when the app stores the user input which isn’t correctly filtered.

It runs within the app.

To prevent these kinds of attacks, we should always filter and sanitizer user input.

SQL Injection

Another kind of attack to prevent is SQL injection.

We run SQL statements in our code dynamically so that we can read data and do malicious actions.

To prevent these attacks, we should use parameterized queries or prepared statements.

Some modules like node-postgres module will let us create a parameterized query as follows:

const q = 'SELECT name FROM books WHERE id = $1';
client.query(q, ['1'], (err, result) => {});

sqlmap lets us automate the testing of detecting and exploring SQL injection flaws in our app.

We can use it to test for SQL injection vulnerabilities.

Command Injection

Another security flaw that can arise is command injection.

We can put commands in a query string to run shell commands.

To prevent these attackers, we should always sanitize user input.

We can write code like:

https://example.com/downloads?file=%3Bcat%20/etc/passwd

Also, child_process.exec runs /bin/sh so it’s a bash interpreter rather than a program launcher.

A new command can be injected by the attacker with this with a backtick or $() .

We can overcome this with child_process.execFile .

Conclusion

We can set cookies and run various commands security.

Also, we need to protect against malicious inputs.