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.

Categories
Node.js Best Practices

Node.js Best Practices — Security and Performance

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 some security and performance considerations when building and running Node.js apps.

Take Care When Working with Child Processes

We’ve to be careful with inputs. This means we should sanitize to prevent any shell inject attacks. We don’t want attacks to run commands with a child process instance that we created from our app.

If we don’t take precautions, we can easily let attackers run commands remotely from our system by entering malicious inputs with unsanitized system commands.

Hide Error Details from Clients

Clients shouldn’t know about the parts of our system or any private data. Therefore, we should hide any information that’s exposed to errors.

We should ensure that we don’t return the entire error object to the client, which may contain some sensitive app details. Sensitive things include file paths, 3rd-party modules, internal workflows of an app, etc., which can be exploited by an attacker.

Configure 2FA for NPM or Yarn

We can use multi-factor authentication to access our private package repositories. This way, attacker’s can’t publish packages that are modified by them.

If these attacks go undetected, then our packages may be exposing data or doing something that we don’t want attackers to do to our system.

Modify Session Middleware Settings

We should remove any information that tells an attacker about our system. One of them if which web framework we use to develop our app. This is because attackers can use this information to look up how to attack our systems.

Therefore, we should hide things like the X-Powered-By response header, which returns the framework that’s used by default in Express.

Avoid DOS Attacks by Explicitly Setting When a Process Should Crash

The Node process will crash when errors aren’t handled. Many suggest that the process should exit even if the error is handled.

If we leave our app to crash when entering certain inputs, then attackers can find the pattern of what inputs make our apps crash. This isn’t good since we don’t want them to find ways to crash our system.

Any time our app crashes, a critical alert should be sent so that we’re aware of it.

Prevent Unsafe Redirects

We should validate user input to prevent malicious redirects so that attackers can’t launch phishing scams to steal user credentials.

Avoid Publishing Secrets to the NPM Registry

Secrets should never in the NPM registry. Otherwise, we increase the chance that we expose the secrets to the public. We can use npmignore file to blacklist specific files or folder, or the files array in package.json can act as a whitelist.

Don’t Block the Event Loop

Since Node.js is single-threaded, we shouldn’t run any synchronous code that takes a long time. This way, we won’t block the event loop so that other pieces of code will run.

Whenever we have long-running processes, we should use asynchronous code like promises so that these kinds of code only runs when the app get a result.

Otherwise, one request can hang the app that thousands of users are using.

Prefer Native JS Methods Over 3rd-Party Utilities Like Lodash

If a function is available in the JavaScript standard library, then we should use it. This way, we need fewer dependencies and therefore, our app will run faster.

Native JavaScript functions are now about 50% faster than utility libraries.

Conclusion

We should validate the inputs of child processes to eliminate risks from malicious commands being entered and injected by attackers. Unsafe redirects should be checked so that users won’t be redirected to malicious sites.

Also, we should hide any information that may be useful to attacks like what framework is used, stack traces, etc. Secrets should never be in the NPM registry.

Performance-wise, we should use JavaScript standard libraries as much as possible to increase performance to reduce the number of dependencies used.