Best Practices for Deploying Node.JS Application for Production with JXcore

Version 1

     

    Node.JS ecosystem is growing rapidly. There are hundreds of thousands of modules, applications and solutions out there, come of which are used in production. While not all developers are concerned or familiar with the best practices for the development process, even less are aware of good techniques and methodologies, which are critical for production stages. In this article, I put together some tips and advices on developing Node applications for production and how it can be improved using some of JXcore’s capabilities.

     

    A typical Node.JS application uses external dependencies (3rd party modules). It doesn’t have to, but using already developed and tested tools will greatly speed up your development. All of those dependencies need to be deployed on production platforms together with the application itself. And if they are npm modules, they can be easily installed with `npm install` (or `jx install`) command which is perfect for development. However, this approach is not ideal for production.

     

    Bundle as many dependencies as you can

     

    While they can be easily installed with `jx install`, you need to remember, that the installation process uses the Internet connection to public or private repositories and it doesn’t have to be just one. If there are few of them – that means they are on different servers, probably even at different locations with different connection timeouts and possible routing problems. Especially for large projects with multiple dependencies (and dependencies of dependencies) - installing all of it at deploy time is much dependent of the Internet connection quality at specific place and time. For that reason it is advised to bundle as many dependencies as you can and deploy together with application without installing it on the target machine.

     

    Obviously not all dependencies can be bundled so easily. Even if they are pure JavaScript files they may require some extra installation steps to be performed by running specific scripts. Also, many of the npm modules, which use native add-ons, need to be rebuilt on target machines prior to the first use. But there is a solution for this too. While preparing the dependencies for the deployment package in your development environment, you can use a special `--ignore-scripts` switch for `jx install` command , like:

    jx install --ignore-scripts

    This downloads all the packages locally, but without executing the scripts defined in package.json file. Let’s suppose you packed your entire application together with node_modules and you have made a deployment onto the target machine. Now, what is left to do is to call:

    npm rebuild

    and it will go through all dependencies and rebuild what’s necessary.

     

    Note that I used `npm rebuild` intentionally rather than `jx npm rebuild` which should work as well. However, at the time of writing this article, JXcore used the older version of npm (1.1.14), which apparently doesn’t rebuild recursively through folders/dependencies, as e.g. npm 2.13.3 would do.

     

    Always use specified version of your dependencies

     

    Another important point is that the dependency modules may be (and they often are) subject to frequent changes introduced by their authors. That’s a good thing for those modules as they are usually getting improved. But for you it brings the possible risk of not being compatible with your application any more. A module that was working with your app yesterday, may not work a week later after being updated. Many things may change: specification of module’s usage, default values of options, API calls may go renamed or even depreciated.

     

    As a result, you should stick to the exact version of modules used in your solution by defining their version numbers in package.json dependencies. For example:

     

    "dependencies": {
      "fstream": "0.1.31",
      "tar": "0.1.20",
      "commander": "1.1.1"
    }
    

     

    It is recommended to avoid any entries allowing dynamic version matching, like:

     

    "dependencies": {
      "fstream": "0.1.x",
      "tar": "latest",
      "commander": "*"
    }
    

     

    The use of “latest” and “*” is particularly discouraged.

     

    Shrinkwrap

     

    If you still want to use `jx install` on target machine, the notations described above may still be used safely once you utilize a special `jx npm shrinkwrap` command to walk the dependency tree and mark down exact version for each. The result of this process is saved into npm-shrinkwrap.json file and may be bundled together with the application. This way, running `jx install` for a solution on a target platform will use that file to download specified versions.

     

    Always test any new version of the dependency

     

    Obviously you don’t have to lock yourself up with dependency versions released long time ago. You can use the new ones, but make sure you have tested them thoroughly with your code. And again, use the new version number explicitly in package.json afterwards.

     

    Use version control for deployments

     

    Consider using a popular revision control systems (e.g. git) to create your deployment packages. There are many debates, whether to upload at all a node_modules folder into the github repository. However, most agree that bare (or pure) npm modules may be uploaded and kept there. “Bare” here means without platform specific binaries. That would work well with our ` jx install --ignore-scripts` command, which does not create binaries at the package preparation stage. However, they may easily be recreated on the target platform by calling `npm rebuild` command mentioned above.

     

    Native package deployment

     

    As an alternative to previously mentioned github deployment you may also use JXcore native packaging feature. This allows you to pack your entire application into a standalone executable. This produces a single file that can be deployed on target platforms. In this case JXcore doesn’t need to be installed on deployment machines. A simple command can be used to prepare a package:

    jx package myapp.js –native

    As a result, you’ll get myapp binary (Unix systems) or myapp.exe (Windows). They are ready to be launched without JXcore as they are executables, but they will work only on the platforms they were built on. For example myapp.exe will not work on Ubuntu and vice versa. Furthermore, different Linux distributions also need to have their own packages prepared. So, if your target platforms are, e.g.: Ubuntu, RedHat and Debian – you need to create native packages separately for each of them. This means you also need separate development platforms, where these package can be created.

     

    Native package deployment approach satisfies the recommendations described above concerning bundling the dependencies and using their specified versions.

     

    However, you should keep in mind about native packages that, currently, projects using native node addons cannot work under a native package. You’ll get a warning when trying to build a package for this kind of solutions.

     

    Make sure your app will recover

     

    After the successful deployment of your application it is important to make sure, that it will be running continuously. There are many so-called “supervisors” available, but JXcore already offers a similar functionality. It is called Process Monitor. Just execute the following commands:

    jx monitor start

    jx monitor run myapp.js

    And your application will run continually, even after occasional crashes. It will be restarted automatically by the Process Monitor.

     

    Also the native packages may be monitored similarly. The usage is exactly same – just use an executable package name as an argument. Run the following command on Windows systems:

    jx monitor run myapp.exe

     

    It is important to mention that the Process Monitor starts monitoring the application only if it stays alive for a defined period of time. By default it is set to 2000 ms, but can be configured through jx.config file (it needs to stay in the same folder as jx binary used to start a monitor). The delay implies, that if the application crashes before the elapse time, the Process Monitor will not start monitoring. Thus make sure to read and output displayed immediately after the `jx monitor run` command has been executed.

     

    Remote check if application is alive

     

    There is a new feature in JXcore's Process Monitor called Heartbeat Check. It allows for remote verification if the monitored application is currently alive. The feature is disabled by default. To enable, you need to add some configuration options to previously mentioned jx.config file. For example:

     

    jx.config

    {
      "monitor": {
        "remote": {
          "port": 17778,
          "https": {
            "key": "/path/server.key",
            "cert": "/path/server.crt"
          }
        }
      }
    }
    

     

    Now when you run the monitor as usual, you should see a message informing, that feature is enabled, e.g.:

    Starting JXcore monitoring service.

    JXcore monitoring is started.

    Remote access enabled on HTTPS, on all IP addresses and port 17778

     

    How do you remotely access this feature and find out if the application is still running? Use the following code on your target machine:

     

    var appPathOnServer = "/var/www/vhosts/kriswebspace.com/httpdocs/index.js";
    
    var options = {
      host: "31.193.134.133",
      port: 17778
    };
    
    jxcore.monitor.checkPath(appPathOnServer, options , function (err, msg) {
      if (err)
        jxcore.utils.console.error(msg);
      else
        jxcore.utils.console.info(msg > 0 ? "alive" : "not alive or mot monitored");
    });
    

     

    This code will tell return `msg = “0”` if the application is not alive or not monitored. Otherwise `msg` will contain string number of how many instances of this app are currently running.

     

    Last words

    It is important to consider the application parts for both development and production scenarios. Some useful developer time dependencies and console logs may slow down the application’s production performance. Centralize your logging efforts. i.e. Winston (see npm winston) might be good candidate for your centralized logging needs.

     

    Production (mostly) means that the application will be up and running 24/7. Test your application against date time changes to see if there are any unexpected behavior. Your web application might become popular over-night.  Use load tests to measure your server’s performance and what happens under high traffic.

    Disclaimer:  This document was prepared by Nubisa, Inc. (creators of JXcore).  For further details and recommendations on how leverage JXcore in a production environment, we recommend contacting them at info@nubisa.com