Hapi.js, Azure Websites and continuous deployment

Lately, I've been experimenting with hapi.js on Azure Websites and I got to the point where I wanted commits to get deployed automatically. In this post, I'll run through the steps to get a basic continuous deploy setup from GitHub going. The full code for this is available at https://github.com/robjoh/hapi-basic-cd

Get Hapi

If you are unfamiliar with hapi.js, it's a great node.js framework for building web applications and services. Assuming Node.js and npm are already installed, from a console I'll get the project started with the following command. For now, I just accept the default values for npm init

>md hapi-basic
>cd hapi-basic
>npm init 
>npm install --save hapi

Hello service

For the purpose of this post, I'll just create the simplest hapi service possible in a file index.js.

var hapi = require('hapi');  
var server = new hapi.Server();

server.connection({  
    host: process.env.HOST || 'localhost',
    port: process.env.PORT || 8080
});

server.route({  
    path: '/',
    method: 'GET',
    handler: function(request, reply) {
        reply('hello');
    }
})

if (!module.parent) {  
    server.start(function() {
        console.log('Server started: ' + server.info.uri);
    });
}

With that, the service can be run from the console. You can try it out with your http client of choice.

>node index.js
Server started: http://localhost:8080  
>curl http://localhost:8080/
hello  

What about tests

Lab is the test utility of choice for hapi. I install it as a dev package along with the code assertion library.

>npm install --save-dev lab code

Lab expects tests to be in a test folder by default, so here's a basic test in test/index.js

var hapi = require('hapi'),  
    Lab = require('lab'),
    lab = exports.lab = Lab.script(),
    code = require('code'),
    describe = lab.experiment,
    it = lab.it,
    expect = code.expect;

describe('hello service', function() {  
    var server = require('../index.js');

    it('says hello', function(done) {
        var options = {
            method: 'GET',
            url: '/'
        };

        server.inject(options, function(response) {
            expect(response.statusCode).to.equal(200);
            expect(response.result).to.equal('hello');
            done();
        });
    });
});

To lay groundwork for later, I'm going to use npm to perform the test task. So I need to change package.json to have the scripts.test value be lab.

{
  "name": "hapi-basic",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "lab" //<-- changed to lab
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "hapi": "^8.2.0"
  },
  "devDependencies": {
    "code": "^1.3.0",
    "lab": "^5.2.1"
  }
}

Now I can use npm to run the tests.

>npm run-script test
.
1 tests complete  
Test duration: 0ms  
No global variable leaks detected  

To the cloud

I'll use the azure cli for the azure portion of this. It can be installed via npm.

>npm install -g azure-cli

First, I need to grab a copy of the default azure websites deployment script so I can make a tweak or two.

>azure site deploymentscript --node
info:    Executing command site deploymentscript  
info:    Generating deployment script for node.js Web Site  
info:    Generated deployment script files  
info:    site deploymentscript command OK  

This creates a few files that azure will use during deployment. I am going to modify deploy.cmd to tell it to run tests as part of the deployment. In the Deployment section of the file:
-changed step 3 to remove the --production flag from install. Otherwise it won't install the lab and code dev pacakges.
-add step 4 to run the tests.

:: 3. Install npm packages
IF EXIST "%DEPLOYMENT_TARGET%\package.json" (  
  pushd "%DEPLOYMENT_TARGET%"
  call :ExecuteCmd !NPM_CMD! install 
  IF !ERRORLEVEL! NEQ 0 goto error
  popd
)

:: 4. Run Unit  Tests
pushd "%DEPLOYMENT_TARGET%"  
call :ExecuteCmd !NPM_CMD! run test  
IF !ERRORLEVEL! NEQ 0 goto error  
popd  

Now I'll go ahead and create the azure website

>azure site create hapi-basic-cd

In prep for git I add a .gitignore file to make sure the node_modules and any log files are not included.

node_modules  
*.log

Create the git repository, add and commit the code

>git init
>git add --all
>git commit -am "Initial commit"

Now I create the repository on GitHub and push to it.

>git remote add origin https://github.com/robjoh/hapi-basic-cd.git
>git push -u origin master

Lastly I connect the azure website with the GitHub repository. In the azure management portal for the site, I select Set up deployment from source control, choose GitHub and authorize the connection which sets up the deployment hook.

Now, any time changes committed to the GitHub repository are automatically deployed. Additionally, since tests are run, any failed test will roll back the deployment.

Wrapping up

This only scratches the surface of many scenarios available with Kudu, which is the engine behind the deployments, along with web hooks which could be used for deployment notifications, deployment slots for staging and much more. I hope to try out many of these over the coming months. Send me a tweet @robjoh if any are of particular interest.