diesel-nodejs
Original Photo by Dan Paul / Unsplash

Welcome

NodeJS has made it simple for anyone to develop robust and secure services and APIs for everything from financial services to robotics. Heck, it even runs on my phone :)

What follows is a collection of best practices that I have curated through experience and education (and occasionally by accident). I hope you find them useful as you move your app from the laptop to production.

In Development

Protip: Plan for deployment before it's time to deploy.

  1. Automate Your Process
    The web development ecosystem is a thriving mass of opinionated toolings. While I won't prescribe a specific tool or system, I can't stress enough the importance of choosing a build system early and automating as much as possible. Excellent choices for development include Gulp, Grunt, Yeomen, or Gradle.

  2. Take Care of the Environment
    Use your development environment's command line to set configuration options as environment variables. This can seem difficult at first, but goes hand-in-hand with automating your deployments. For example, if we set

    
     # sets Node options for development
     set NODE_ENV=development
     # db auth
     set DEV_DB_USER=dev_user
     set DEV_DB_SECRET=dev_password
     set PROD_DB_USER=prod_user
     set PROD_DB_SECRET=prod_password
     

    then, in Node, you can access those variables like this

              
         var DB_USER, DB_PASS = undefined
         
       if (process.env.NODE_ENV == "production") {
           DB_USER = process.env.PROD_DB_USER
           DB_PASS = process.env.PROD_DB_PASS
       } else {
           DB_USER = process.env.DEV_DB_USER
           DB_PASS = process.env.DEV_DB_PASS     
       }
       
       

    Now we aren't hard coding security credentials in our code! This also allows you to have conditional code based on the execution environment. Read on for an example of conditional logging.

  3. Clean Up Your Logging
    Excessive console logging will slow down your app. While you could simply comment out your debug statements, there are better options.

    1. Use conditional logic. If you are using set NODE_ENV=production (and you should be!) you can wrap your development code in blocks like this

       
       if (process.env.NODE_ENV != "production") {
          console.debug('Here be dragons!')
          // other debug code
       }
       
       
    2. Use a log manager such as Winston. A log manager will allow you to create custom loggers and log levels, specify which ones are active, and where they log to for each deployment scenario. For example, with Winston

       
        const winston = require('winston')
        
        if (process.env.NODE_ENV == "production") {
           // configure logging for production
           winston.level = 'error'
        }
       
       
  4. Don't Use Node to Serve Static Assets
    Node's single thread model is not optimized for serving static assets. Instead, serve static assets (static HTML, images, SVG, video, downloadable content) from a reverse-proxy such as Nginx or Apache, or from a cloud storage service such as Firebase Hosting, Azure Blob, or S3. For bonus points enable CDN delivery of those assets using CloudFare, CloudFront for S3, or another CDN provider.

In Deployment

  1. Keep Automating Your Process
    If your deployments are complex and you're planning for growth, automating your infrastructure is important. Do it - you'll thank me later. Depending on your needs, any of the following may be good choices; Ansible, Chef, and Docker. It's also worth noting that if you use cloud providers such as Google and Amazon, that they have a suite of deployment tools and integrations worth considering.

  2. Use a Reverse-Proxy
    Reverse-proxies are like the swiss-army knife of hosting. They sit between internet (hopefully behind a firewall) and your app servers and can handle things like SSL termination, decompression and compression of requests, serving static files, and load-balancing. Allowing a reverse-proxy to handle these items take the load off of your app server and increases its performance. The leading reverse-proxies are Nginx, HAProxy, and Apache.

  3. Use SSL
    If you don't know how to set up certificates, now is a great time to learn. It is also a perfect use case for environment variables. Nginx, Apache, HAProxy, and Node each have SSL configuration options. Also, there are services providing free certificates (LetsEncrypt) if your provider doesn't offer a service and you aren't quite ready to purchase your own (though you should).

  4. set NODE_ENV=production
    NODE_ENV began as an Express optimization, but many Node modules now makes use of it. Setting this environment variable on your deployment platform allows supporting Node modules to enable production optimizations (such as caching) and disable development features that may impact performance.

  5. Use a Process Manager
    In development Node is generally launched from the command line using the node <server.js> command (or through various dev scripts or tooling). Unfortunately, when Node crashes it remains unavailable without intervention. Using a process manager such as forever or pm2 will help ensure that when a Node process does crash, it is restarted. For larger deployments, consider using container management apps such as Docker and setting the desired restart policy

  6. Make Use of Your Cores
    Being single-threaded, Node will only utilize a single core unless you tell it otherwise. There are two excellent options.

    1. Node Cluster is the simplest solution to taking advantage of multi-core, multi-processor servers. While it can be configured manually, you can easily use the following pm2 command to enable it.

      pm2 start app.js -i max
    2. Use a dedicated load balancer.
      Most reverse-proxy servers can also act as a load balancer.

      To use multiple cores with a load balancer; first launch multiple instances of your node app, each on their own port. Next, configure your load-balancer's server list to point to each launched instance. The benefit of using a dedicated load balancer is in performance and options. Typically a load balancer will significantly out perform Node Cluster as well as give you different algorithms for choosing instances (Node Cluster is round robin). The drawback is in the complexity of configuration.