Package your Node.js app for Cloud with Appsody

The use of Appsody's tools and provided Stacks simplifies the steps and knowledge required to build "cloud packaged", "cloud native" and "cloud functions" based applications.

Appsody's nodejs Stack makes it easy to take any existing Node.js application and make it "cloud packaged".

The following tutorial, shows you how to take a sample Node.js application, and package it into a best-practise container image using the Appsody CLI and the nodejs Stack.

Prerequisites

To follow this tutorial, you need to install both the Appsody CLI and Docker. You also need a Node.js application to cloud package.

Install the Appsody CLI

Install the Appsody CLI by following the Installing Appsody guide for your platform.

Create a sample application

You need a Node.js app to package. You can either use one you already have, or create a sample Express.js app by using the following steps:

  1. Install the Express.js Application Generator:
    npm install -g express-generator
  1. Create a directory for your application:
    mkdir nodejs-app
    cd nodejs-app
  1. Run the Express Application Generator to build a sample application:
    express
  1. Run your generated Express.js application:
    npm install
    npm start
  1. Open your browser to see the application running: http://localhost:3000

This displays the following output:

Express: Welcome to Express Express: Welcome to Express

  1. Stop the Express.js application using Ctrl-C

Your sample application is now ready to use!

Enabling your application with the nodejs Appsody Stack

New Appsody based applications are created by using appsody init <stack> <template>, where the stack is chosen by you from the stacks that are listed when you run appsody list. The init command downloads the most recent copy of the Appsody Stack, and populates the project directory with a template that provides a basic project structure.

In order to enable an existing application with a stack, the same steps can be followed, but passing the option none to appsody init, as a project structure is not required.

  1. Enable the Node.js Application:
    appsody init nodejs none

This provides output similar to the following:

Checking stack requirements...
Docker requirements met
Appsody requirements met
Running appsody init...
Downloading nodejs template project from https://github.com/appsody/stacks/releases/download/nodejs-v0.3.5/incubator.nodejs.v0.3.5.templates.simple.tar.gz
Download complete. Do not unzip the template project. Only extracting .appsody-config.yaml file from /Users/kyemaloy/nodejs-app-2/nodejs.tar.gz
Setting up the development environment
Your Appsody project name has been set to nodejs-app-2
Pulling docker image docker.io/appsody/nodejs:0.3
Running command: docker pull docker.io/appsody/nodejs:0.3
0.3: Pulling from appsody/nodejs
Digest: sha256:c0bc3aef3a2d5a631acfebc8fc33e2dfc230c37a0783d5ae2b6d61f93af1fe39
Status: Image is up to date for appsody/nodejs:0.3
docker.io/appsody/nodejs:0.3
Running command: docker run --rm --entrypoint /bin/bash docker.io/appsody/nodejs:0.3 -c "find /project -type f -name .appsody-init.sh"
Successfully initialized Appsody project with the nodejs stack and no template.

As well as downloading the latest version of the Appsody Stack (in this case version 0.3), this has added a .appsody-config.yaml file to the project, which configures the versions of the Stack that the project will use.

At this point, your application has been enabled, which means you can now run, test and debug your application in a continuous containerized environment provided by the Appsody Stack, to build it into a optimized container image, and to deploy it to Kubernetes.

Developing your application with Appsody

Now that your application has been enabled, you can use the run, test and debug commands to the Appsody CLI to work with your application in a continuous containerized environment provided by the Appsody Stack.

Working with your application

Appsody's run command provides a continuous development environment, where changes that are saved to your project cause your application to be restarted. This is similar to the experience you get from tools like nodemon, but this occurs inside the containerized environment. The only requirement is that your application's package.json file needs to include a start script.

The following steps show you how that works for your application:

  1. Open your application in your favourite IDE

Alternatively you can open a second terminal window so that you can edit the application files.

  1. Start the continuous run environment, by using the Appsody CLI in the terminal window:
    appsody run
  1. Open your browser to see the application running: http://localhost:3000 This will display:

welcomeToExpress

As appsody run provides a continuous development environment, any code changes you make will cause an automatic restart of the application.

  1. Make a code change to your project that will be reflected in the browser:

a) Open views > index.jade

b) Change the file contents to:

    extends layout

    block content
      h1= title
      p Welcome to #{title}!!!!!!!

c) Save the file

  1. Open your browser to see the application running: http://localhost:3000 This will display:

Express: Welcome to Express!!!!!!! Express: Welcome to Express!!!!!!!

You may need to refresh the page to see the change.

This provides a continuous, containerized development environment where you can almost instantly see any changes you make reflected in the application running in the browser.

Carrying out Performance Analysis of your application

As part of the development environment, both appsody run and appsody debug also provide a performance monitoring and profiling dashboard that uses the appmetrics-dash module. This feature is available during development only and it enables you to do analysis of your application.

  1. Open the Performance dashboard: http://localhost:3000/appmetrics-dash/

This provides a display similar to the following, which provides data for:

  • HTTP request responsiveness and HTTP throughput

  • Performance of outbound HTTP requests and other requests

  • Event Loop latency showing the delay before tasks are scheduled.

  • CPU and Memory usage for the process and the system

Performance Monitoring and Analysis Dashboard Performance Monitoring and Analysis Dashboard

  1. Click on the Profiling tab to open the profiling view

  2. Click on the "Hamburger Menu" (the three horizontal lines in the top left-hand corner" and select Enable Profiling

This starts to collect function profiling data from the application, and to visualize it using a flame graph, which provides a visualization of the stacks and functions that are using most CPU time.

Flame Graph generated from Performance Profiling Data Flame Graph generated from Performance Profiling Data

  1. Click on the "Hamburger Menu"(the three horizontal lines in the top left hand corner) and select Disable Profiling

As well as providing performance monitoring and profiling, you can also trigger Heap Dumps and Node Reports through the menu, and see an overview of your application in the Summary tab.

  1. Finally, stop the continuous run environment by either:
  • Using Ctrl-C in the terminal window where appsody run is executing

  • Running appsody stop from the project directory

Debugging your application

Appsody's debug command provides the same continuous development environment that you get with the run environment. In addition, the application runs in debug and provides the correct configuration to enable remote access from a local debugger.

The following steps show you how that works for your application:

  1. Open your application in your favourite IDE

Alternatively you can open a second terminal window so that you can edit the application files.

  1. Start the continuous debug environment, by using the Appsody CLI in the terminal window:
    apposody debug
  1. Open your browser to see the application running: http://localhost:3000 This displays:

Express: Welcome to Express!!!!!!! Express: Welcome to Express!!!!!!!

As appsody debug provides a continuous development environment, any code changes you make will cause an automatic restart of the application. It also sets up the container to allow remote access from a debugger.

  1. Attach a debugger to your application

You can either use your favourite debugger for this, or use the one integrated into the Chrome Browser as follows:

  1. Open the Chrome Browser

  2. Enter chrome://inspect in the URL bar

chrome://inspect in the search bar chrome://inspect in the search bar

It displays a view similar to the following in the window:

Available targets to inspect with the Chrome Debugger Available targets to inspect with the Chrome Debugger

  1. Select the inspect link for the application shown under Remote Target

It opens a new panel containing the debugger, attached to your application running inside the continuous containerized debug environment.

You can now debug as you would normally. Additionally, any code change that you make to the project through changes to its source code with automatically stop and restart your application (after which you will need to restart the debugger connection).

  1. Finally, stop the continuous debug environment by either:
  • Using Ctrl-C in the terminal window where appsody debug is executing

  • Running appsody stop from the project directory

Testing your application

Appsody's test command makes it possible to run any tests that your application has inside the containerized environment. To use this capability, your application needs to be provided with some tests, and your package.json file needs to include a test script that runs them.

The steps below show how that works for your application. As the sample application created from the Express Application Generate does not include any tests, you first need to add one.

  1. Replace your package.json file with the following code. This will add devDependencies for chai, mocha and request, and add a test script:
    {
      "name": "test",
      "version": "0.0.0",
      "private": true,
      "scripts": {
        "start": "node ./bin/www",
        "test": "mocha"
      },
      "dependencies": {
        "cookie-parser": "~1.4.4",
        "debug": "~2.6.9",
        "express": "~4.16.1",
        "http-errors": "~1.6.3",
        "jade": "~1.11.0",
        "morgan": "~1.9.1"
      },
        "devDependencies": {
        "chai": "4.2.0",
        "mocha": "~6.1.0",
        "request": "2.88.0"
      }
    }
  1. Create a new test directory in your project:
    mkdir test
    cd test
  1. Create a new test > test.js file containing:
    const expect = require('chai').expect;
    const request = require('request');
    const app = require('../app');
    var http = require('http');

    const PORT = process.env.PORT || '3000';
    const server = app.listen(PORT);
    const url = "http://localhost:" + PORT;

    describe("Node.js Stack", function () {

      // Testing / responds with 200 OK
      describe('/ endpoint OK', function () {
        it('status', function (done) {
          request(url + '/', function (error, response, body) {
            expect(response.statusCode).to.equal(200);
            done();
          });
        });
      });
    });

    // Stop the server after testing
    after(done => {
      server.close(done);
    });
  1. Check that the tests are running locally:
    npm install
    npm test

This responds with output similar to the following:

    Node.js Stack
    / endpoint OK
    GET / 200 309.872 ms — 170
    ✓ status (341ms)

    1 passing (354ms)
  1. Run the tests using the Appsody test mode:
    appsody test

It responds with output that includes the following:

    [Container] > test@0.0.0 test /project/user-app
    [Container] > mocha
    [Container]
    [Container]
    [Container]
    [Container] Node.js Stack
    [Container] / endpoint OK
    [Container] GET / 200 320.079 ms — 170
    [Container] null
    [Container] ✓ status (359ms)
    [Container]
    [Container]
    [Container] 1 passing (375ms)

Note that, unlike run and debug, test executes a single run of the tests rather than a continuous container.

Building your application with Appsody

When you are ready to build a deployable container image for your application, you can do that using appsody build. The build command creates a production-optimized image that is built using the regular “node” Official Docker image from the Node.js community and is then rebased on the communities “node-slim” image. The “node-slim” image is significantly smaller than the regular “node” image, making it easier and faster to deploy to a cloud. It is also more secure because it does not include packages such as compilers (which are required in the regular “node” image so that you can use packages with native add-ons).

The steps below show how to build the container image for your application, and then run it locally using Docker:

  1. Build the container image for your application:
    appsody build

This builds a container image for your application, using the name of your project as the "tag" (which is the name of the folder in which it resides). One of the final lines of the output gives the name of the image, usually just above the running command outputs:

    Built docker image dev.local/nodejs-app
  1. Run the container image using Docker:
    docker run --rm -it -p 3000:3000 dev.local/nodejs-app

This runs your container image, using the -p option to map port 3000 from the container to port 3000 on your machine, the -i option to run interactively (so you can use Ctrl-C to stop the container), the --rm option to remove the container when it's stopped, and the -t option to run the container image with our "tag" of nodejs-app.

  1. Open your browser to see the application running: http://localhost:3000

Note that the Performance Dashboard is not present, as it was injected as a development-only feature.

  1. Finally, stop the container by using Ctrl-C in the terminal window where docker run is executing.

You now have a cloud package available in a container image that is ready to be deployed to any cloud that supports container images.

You can also use the appsody deploy command to deploy the image to any Kubernetes cluster.

Next Steps

This article covered how to take an existing Node.js application and make it "Cloud Packaged" using the nodejs Appsody Stack. Appsody also provides a nodejs-express stack with a preconfigured Express.js server. The nodejs-express stack includes cloud-native capabilities, such as liveness and readiness checks, metrics, and observability.

For more information on Appsody, join us on Slack, follow us on Twitter and star us on GitHub.