Categories
Node.js Best Practices

Node.js Best Practices — Monitoring and Testing

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.

Inspect for Outdated Packages

We can inspect for outdated packages with the npm outdated and npm-check-updates tools.

They detect installed packages which are outdated.

To automate this, we can inject this into the CI pipeline to check during build time.

This way, we’ll update the packages as soon as possible.

Use Production-Like Environment for End to End Testing

We should test our app in a production-like environment to simulate real user interaction.

This is the whole point of end to end tests.

We run the tests in a clean, production-like environment so that we know what will happen if the app goes to production.

Also, we should reset the database to the original seed data after each test so that all tests run independently in isolation.

We can use docker-compose to reset our database when we spin up our environment with it.

Refactor Regularly Using Static Analysis Tools

We can use static analysis tools to detect things that aren’t picked up by linters.

Issues like duplicates and code complexity can only be picked by static analysis tools.

Therefore, we’ve to use them to help us check for those and we can fix them one by one.

Some tools to do the checks include Sonarqube and Code Climate.

Carefully Choose your CI Platform

We need some a CI platform to run all our tests and build our app so we can deploy them easily.

They should make our lives easier.

There are many choices like Jenkins and CircleCI.

We’ve to choose one that has the features we need and have good speed.

Test Middlewares in Isolation

Middlewares should be tested in isolation so that we can catch any bugs earlier.

Since they’re used in many places, any bugs with them will affect many endpoints.

We can stub and spy on the request, response and next function to do our tests.

Monitoring

Monitoring our app is definitely something that should be done all the time.

If there’re problems like when the app goes down, we must know right away.

Otherwise, we’ll have many angry and frustrated users.

They have many graphs and tables to show us what’s going with our app.

Increase Transparency Using Smart Logging

Without logging, our app is a black box.

We wouldn’t know what our app is doing without logs.

Therefore, we need to find a logging solution so that we can easily search and extract log activities.

We need a reputable logging library like Winston to do our logging.

And we need to put in a location that we can visualize and search for the data easily.

The error tracking tools should also give us useful metrics like error rates that we can check.

Conclusion

We should check for outdated packages and automate our builds.

Also, monitoring and smart logging are essential.

Categories
Node.js Best Practices

Node.js Best Practices — Monitoring

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.

Delegate to a Reverse Proxy

We should delegate whatever we can do with a reverse proxy to it.

Things that they can do including SSL, gzip, etc.

This way, our app has to do less, which means it uses fewer resources.

We don’t want our app to be busy doing infrastructural tasks instead of what it’s supposed to do.

Lock Dependencies

To avoid issues with different package versions, we should lock the dependencies of our project.

We can do that by using config files with .npmrc to tell NPM which exact version is required.

Alternatively, we can use npm shrinkwrap to lock the dependencies to the ones we want.

Guard Process Uptime Using the Right Tool

We should check for uptime with process management tools like PM2.

This way, our Node app will be restarted if any errors are encountered.

Utilize All CPU Cores

If we have multiple CPU cores in our server, then we should use them all.

We can use Node Cluster or PM2 to manage the clusters for small to medium-sized apps.

For larger apps, we can replicate the process using some Docker cluster or deployment scripts based on Linux’s init system.

Otherwise, we won’t use the full power of our server to serve our app,

Create a Maintenance Endpoint

We can create a maintenance endpoint to return the data that we want to get from the app.

This way, we can get all the information we need in one place.

We don’t want to do diagnostic deploys just to find out some data.

Discover Errors and Downtime using APM Products

APM products are great for finding out errors, downtime, and slow code in our app.

It goes beyond traditional monitoring and monitors the user experience of our app as a whole.

They can find the root cause of performance issues.

Make Our Code Production-Ready

We’ve to plan for production since day 1.

Our app should be stateless. So it shouldn’t save data locally.

A cache should be used anywhere to increase performance.

Memory usage should be checked since the beginning.

We can check for them with tools like memwatch.

Naming functions help us trace our code when there are any functions.

CI tools are useful for detecting failures before sending our code to production.

Logging data should have contextual information.

And there should be an ID that describes the transaction.

Error handling should be done to prevent crashes.

Measure and Guard Memory Usage

We should make sure that our app don’t use too much memory.

If it does, then we should cap it and make sure our app never uses beyond that amount.

Also, we may gauge memory usage periodically with shell commands for small apps.

But for larger apps, we need to add a built-in memory monitoring system.

Conclusion

We can watch for issues with various monitoring products.

Also, we should all the resources we can get, but keep memory usage in check.

Categories
Node.js Best Practices

Node.js Best Practices — Config and Errors

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 Environment Aware, Secure and Hierarchical Config

We should make our app configurable.

To do that, we can read the settings from environment variables.

For instance, we can use the dotenv library to read environment variables from a .env file and other sources.

This will make our app run properly anywhere.

Use Async-Await or Promises for Async Error Handling

Async code should be used for any long-running operation.

The worst way to chain async code is to have multiple levels of nested async callbacks.

They’re hard to read and debug.

The best way is to use promises and chain them.

To make them even shorter, we can use async and await.

So either, we write:

return functionA()
  .then(functionB)
  .then(functionC)
  .then(functionD)
  .catch((err) => console.error(err))
  .then(alwaysRunThisFunction)

or:

async function executeAsyncTask () {
  try {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    const valueC = await functionC(valueB);
    return await functionD(valueC);
  }
  catch (err) {
    console.error(err);
  }
  finally {
    await alwaysRunThisFunction();
  }
}

They do the same thing.

The callbacks all return promises so we can chain them.

Use Only the Built-in Error Object

When we throw errors, we should use the built-in error type.

For instance, we should write:

new Error('whoops!')

instead of:

throw ('whoops!');

The Error instance has information like stack traces that aren’t available anywhere else.

Distinguish Operational vs Programmer Errors

We should distinguish operational errors like API errors where they can be handled properly.

Programmer errors are errors like undefined errors that we didn’t take into account.

Programmer errors should be fixed so that our app will run smoother.

Operational errors should be handled by our code.

Handle Errors Centrally, not Within an Express Middleware

Errors should be handled centrally rather than with an Express middleware.

Otherwise, we’ll get duplicated code in multiple places to handle the same error.

For example, instead of writing:

app.use((err, req, res, next) => {
  logger.logError(err);
  if (err.severity == errors.high) {
    mailer.sendMail(configuration.adminMail, 'error occured', err);
  }
  next(err);
});

We write:

module.exports.handler = new ErrorHandler();

function ErrorHandler() {
  this.handleError = async (err) => {
    await logger.logError(err);
    await sendEmail;
    await saveInOpsQueueIfCritical;
    await determineIfOperationalError;
  };
}

We have a central error handler we can import into other modules.

Document API Errors Using Swagger or GraphQL

We should document the error s that may occur with our REST APIs with Swagger or GraphQL.

This way, we can use the API and handle the errors that are documented.

If we handle the errors that are encountered, then it won’t crash.

Exit the Process Gracefully

If an unknown error is encountered, we should exit the process gracefully with a process manager.

This can be done with a process manager with Forever or PM2.

These tools can restart our app if any errors occur.

Conclusion

We should document any errors that APIs can throw so they can be handled.

Async code and making our app configurable are also good ideas.

Categories
Node.js Best Practices

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

Use Helmet if we’re Writing a Web App

We should use Helmet if we’re writing a web app.

It does a few things, including:

  • add XSS protection
  • prevent clickjacking with X-Frame-Options
  • enforcing all connection to be HTTPS
  • setting Context-Security-Policy header
  • disable X-Powered-By response header so attackers can’t narrow down the libraries we’re using to write the app.

Helmet will set sensible defaults for all those options.

We can install it by running:

npm install helmet

In our Express app, we can use the middleware by writing:

const helmet = require('helmet');  
app.use(helmet());

Monitor our Applications

If we’re running our app, then we need to monitor it.

Users aren’t happy if our app goes down and there’s no quick resolution.

Therefore, we need to monitor our app and alert everyone so that we can get it running again quickly.

For instance, KeyMetrics.io integrates with PM2 to check for the app’s health.

A dashboard is also provided to show us when it’s up or not.

Latency and events can be checked.

Test our Code

We can test our code with automated tests so that we have peace of mind when we change our code.

They run quickly and automatically so that we don’t have to check every part of our app ourselves.

We should add tests when we fix bugs and run our tests regularly.

There’re a few ways to run tests.

We can use Mocha, Chai, Jest, or Jasmine to run them.

They’re all popular and provide equivalent functionality.

To create tests that make requests, we can use Supertest to make the requests and check the results.

Structure Solution by Components

We should structure our project by components.

This way, we can find them later.

It’s easy to get lost if a project has no structure.

We should divide our code into modules.

Layer Our Components and Keep Express within its Boundaries

We should only use Express for the controller portion of the app.

Business logic should be in their own modules to organize our app better.

This way, each module, and function does its own thing.

Mixing different parts together makes them hard to test and maintain.

Wrap Common Utilities as NPM Packages

If we have common things that are used in multiple projects, we should put them in their own package.

Common functionality like logging, encryption, etc. should be in their own package.

This way, we only have to change one package to update the functionality.

Separate Express ‘app’ and ‘server’

Our Express app shouldn’t be one big file.

The entry point should be separate from the rest of the app.

One big file makes everything slower.

The API should be in app.js and the networking code should be in www .

The API declaration can also be split into component if it’s big.

Conclusion

We can organize our app better.

Also, we can take steps to improve security and monitor our app.

Categories
Node.js Best Practices

Node.js Best Practices — Error Handling

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.

Handle Errors

If there’s any chance that an error may occur with a piece of code, we should handle the error.

For example, if we have a change of promises, we should call catch with an error handler callback passed into it.

We can write:

doSomething()
  .then(doNextStage)
  .then(doMoreThins)
  .then(updateInterestedParties)
  .then(cleanUp)
  .catch(errorHandler);

We have the catch method with the errorHandler callback function passed into it.

The callback will be called when any of the promises in the chain throws an error.

Ensure our App Automatically Restarts

We should ensure that our app automatically restarts.

This way, we won’t have to do this ourselves when our app encounters an unhandled error.

We need a process manager like Nodemon, forever, or PM2 to do this.

The last 2 are for production usage.

Nodemon is used for development more often.

For instance, we can install PM2 globally with:

npm install pm2 -g

Then we can launch our Node app with:

pm2 start app.js

Cluster our App to Improve Performance and Reliability

We can course our app so that we have more than one instance of it.

This way, we can distribute the work across all cores.

We need this since Node apps run in a single process.

PM2 lets us do this easily by running:

pm2 start app.js -i max

This lets us run multiple processes equal to the number of cores present in the server.

The processes don’t share memory or resources.

They’ll all have their own connection to the database, for instance.

Therefore, we should use something like Redis to keep session state across all instances.

Require All Dependencies Upfront

It’s a good idea to have our require calls all at the top so we won’t have to look for them in various parts of our code.

Also, we can spot any issues with them earlier.

For example, we can write:

const datastore = require("db")(someConfig);

app.get("/my-service", (request, response) => {
  db.get(req.query.someKey)
  // ...
});

We call require at the top so that we run them all earlier.

If there’re any errors with them, they’ll be thrown at the beginning.

Also, require is synchronous, so if it takes a long time to run, it’ll hold up our app.

If there any problems like these, we’ll find them early if we require them early.

Use a Logging Library to Increase Errors Visibility

Errors can happen in any environment.

And we want them to be visible so that we can fix them.

console.log is very limiting in a production setting.

A better logging library can give us more control over how to log.

We can have various types of logging or save the data somewhere.

Some good libraries include Loggly and Winston. Winston is the base package for basic logging.

And Node-Loggly uses Winston for logging.

This way, we’ll see the errors in one central place that’s searchable.

Conclusion

Error handling can be made easy with a few changes to our code.

We should catch errors and log them so we can fix them is needed.