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.

Categories
Node.js Best Practices

Node.js Best Practices — HTTPS

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.

Secure Transmission

Without HTTPS, communication is done by sending clear text.

This means that anyone can tap into the communication channel to get the data.

Therefore, we should communicate via HTTPS.

We can use strong ciphers to encrypt our communication so that we don’t have to worry about insecure communication.

Also, we’ve to test for ciphers, keys and renogotiation is properly configured.

And certificates have to be valid.

We can use tools like nmap and sslyze to check these things.

For instance, we can run:

nmap --script ssl-cert,ssl-enum-ciphers -p 443,465,993,995 www.example.com

to check the certificate from example.com

With sslyze, we can run:

./sslyze.py --regular example.com:443

to do the check

HSTS

We should add the Strict-Transport-Security header to enforce HTTPS connections to the server.

Headers like:

strict-transport-security:max-age=631138519

would be returned from the web app if this exists.

We can check:

curl -s -D- https://example.com/ | grep -i Strict

to check for if there’s any header that starts with Strict to check the response for the header.

Denial of Service

To prevent denial of service attacks, we should take some steps to prevent them.

If the user tries to log in too many times and failed, we should lock them out for a period of time.

This mitigates brute force guessing attacks.

A small number of login attempts prohibits more login attempts for a period of time.

It can be a couple of minutes, then increased for subsequent failures.

There’re also regexes that can hang apps.

Patterns like grouping with repetition with patterns that have more repetition inside or alternation with overlapping are regex that’ll crash our Node app.

For example, patterns like (a+)+ are vulnerable regexes that cause heavy computations.

To check for these regexes, we can use the safe-regex package.

Error Handling

We should be careful with error handling in that we don’t leak sensitive information about our app.

They can reveal information that are useful to the attacker.

Therefore, we should log them rather than show them to the user.

NPM

We should check the packages that we’re requiring in our Node app.

To check for vulnerable packages, we can use the nsp package.

We run:

npm i nsp -g

to install the package.

Then we audit the shrinkwrap with:

nsp audit-shrinkwrap

Or we can check package.json :

nsp audit-package

Serverless

Serverless starts with the introduction of AWS Lambda.

It’ll become a popular way to build apps.

We can create code that runs straight from the server.

The Structure of a Node.js Application

We create a Node app first with npm init .

This will create package.json which has the metadata for our Node project.

They include the name, version, description, scripts, and more.

It also has the package information.

Conclusion

We can analyze the security of our app with a few tools.

And we can structure our app by creating a package.json with npm init .

Categories
Node.js Best Practices

Node.js Best Practices — Express Apps

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 Gzip Compression

We should use gzip compression to compress our assets.

This way, the user needs to download less data to their computers.

We can do that with the compression middleware with Express:

const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())

For high traffic sites, we should enable gzip compression in our reverse proxy.

Don’t Use Synchronous Functions

Synchronous functions are definitely a bad idea for many parts of the app.

Any potentially long-running code should be async.

This way, they won’t block the rest of the app from running’

Synchronous calls can add up and slow down our app significantly.

We can use the --trace-sync-io command-line flag to print warnings and a stack trace whenever synchronous code is run.

This is useful for tracing synchronous code in the development environment.

Do Logging Correctly

To do logging correctly, we should use better loggers than console.log or console.error .

Some libraries like Winston can help with logging.

It has much better features than the console methods.

The debug module is useful for logging messages for debugging.

We can enable debugging with it with the DEBUG environment variable.

Handle Exceptions Properly

We can handle exceptions with try-catch or catch with promises.

Also, we should ensure our app automatically restarts when an unhandled error is encountered.

The best way is to avoid our app crashing.

What we shouldn’t do is to listen to the uncaughtException event.

It’s emitted when an exception bubbles all the way back to the event loop.

Adding an event listener to the uncaughtException event will change the default behavior of the process than encounters the exception.

The process will continue to run despite the exception.

Then the state of the process becomes unreliable and unpredictable because of this.

We can use try-catch with synchronous code:

app.get('/search', function(req, res) {
  const jsonStr = req.query.params
  try {
    const jsonObj = JSON.parse(jsonStr)
    res.send('Success')
  } catch (e) {
    res.status(400).send('Invalid JSON')
  }
})

We can call catch with promises:

app.get('/', function (req, res, next) {
  queryDb()
    .then((data)  => {
      // ...
      return makeCsv(data)
    })
    .then(function (csv) {
      // ...
    })
    .catch(next)
})

app.use(function (err, req, res, next) {
  // handle error
})

We have next in catch which is called when an error occurs.

This will call the middleware below it.

Set NODE_ENV to “production”

We should set NODE_ENV to production so that Express cache view templates, cache CSS files, and generate less verbose warnings.

These changes will make the app speed up by a factor of 3.

Ensure Our App Automatically Restarts

Our app should automatically restart when an error occurs.

This way, it won’t go offline when it crashes.

We can do this with a process manager.

It would restart the app when the Node app crashes.

The init system provided by our OS may restart the process without a process manager.

Conclusion

We should make some basic changes like gzip compression, ensure automatic restart, and catch errors.