Your second Heroku Node App

Posted on Wednesday, April 29, 2015




Recently I created a Heroku account and followed Heroku's guide on setting up and deploy a NodeJS app.  Their step by step guide was great and very helpful.  I got my hello world app working.  

Now that I have gone through the guide I want to start from zero.  I want to create a simple NodeJS express app and push it up toHeroku to run.








Poking around


Go to https://www.heroku.com/ and login







My app is still installed from last time.




 
Oh!  Look it's sleeping.

It's my understanding that Apps go to sleep when not in use and take a second or two to "Wake up".  But,  if you have more than one server it will remain awake all the time and be very responsive. (Don't quote me on that)




Click on the app to open it.












Here is what I see.







Click this guy to open the Application.





There is my webapp







Going back to the first screen I can see the app is now "Awake"








Click on Production Check.  There is some good info there.  Like that you need more than 1 server to have it not go to sleep.






Remove the app


… How do I remove this app so I can start clean?





Click on Settings









Scroll to the bottom and click Delete app…





Enter the apps name, which is shown on the top, click Delete App.






It's gone







Reloading the page results with this message "No such app"







Create a Basic Express App


I am going to create a very basic Express app using TDD (Test Driven Development),  save it to a git repo and push it to a git server I have.  Then I am going to figure out how to push it up and run it on Heroku.

Create a new folder and run npm to initialize it.


  > mkdir MyApp
  > cd MyApp
  > npm init







After all that I should have a package.json.   Here is mine.


{
  "name": "MyApp",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "mocha --recursive test"
  },
  "author": "Patrick Bailey",
  "license": "ISC"
}




Edit the package.json file


  > vi package.json


Add the following


{
  "name": "MyApp",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "private": "true",
  "scripts": {
    "test": "mocha --recursive test"
  }, 
  "author": "Patrick Bailey",
  "license": "ISC"
}


I am going to make this a private module (So it can't accidentally get published to npm). 





Install mocha and chai


I am going to use mocha and chai for TDD (Test Driven Development).  I am still getting used to Node and npm so I may do this next part wrong, but I am trying my best to get it right.

If I run


  > npm install mocha


It creates the node_modules folder, if it does not exists, and downloads the mocha libraries and its dependencies.  But it does not update the package.json file.  

Since it's not in the package.json, as a dependency, when someone else gets my app and they run "npm install" they won't get this module downloaded.


I could install it globally


  > npm install -g mocha


Then I can run it from the command line.  Of course I would then need to make sure whoever runs my test script also has it installed globally.



I could install it locally and save it to the package.json


  > npm install -S mocha


That installs it in node_modules and updates package.json with mocha as s dependency.



Saving the module does not seem the way to go for a module solely used for testing.  Mocha and chai are modules I don't want to deploy to production.   What do I do? 

Poking around…



Let me uninstall mocha


  > npm uninstall -S mocha


Using the -S removes it from package.json

Install it with --save-dev


  > npm install --save-dev mocha




Now it's a devDepenencies.



This is still a little confusing…
Mocha has been installed locally in the node_modules.

If I remove node_modules and run npm install


  > rm -r node_modules
  > npm install


I get back mocha in npm_modules… ?


If you want Production only, ie no devDependencies, you need to run npm install --production (or you can set the NODE_ENV variable to production).



  > rm -r node_modules
  > npm install --production
  > ls node_modules




Since that was my only dependency I don't download any modules.

OK, those explanations helped me a lot to understand what is going on.

Let me fix this (I don't want to be in production mode on my box)


  > npm install




Install chai with --save-dev


  > npm install --save-dev chai


Install superagent, I will be using it for testing. 


  > npm install --save-dev superagent



Install Express


Install Express


  > npm install -S express





Create bare bones express app


Create app.js


  > vi app.js




Place the following in it.


 var express = require('express');

var app = express();
var server;

var start = exports.start = function start(port, callback) {
   
server = app.listen(port, callback);
};

var stop = exports.stop = function stop(callback) {
   
server.close(callback);
};

app.get('/', function sendResponse(req,res) {
    res.
status(200).send('Hello World!');
});



This is very basic.  It returns Hello World.  It also has a function for Starting and stopping the server (this will be used by the tests).


Create server.js for start up

Poking around I found this page http://www.jayway.com/2014/03/28/running-scripts-with-npm/ [3]. Reading this I see that you get some default scripts with npm.   The one I am looking at is "start".  npm has a default start script, it is

"start": "node server.js",

With that in mind I think it may be a good idea to create a server.js file that is my start up file.

Create server.js


  > vi server.js


And place the following in it.


var app = require('./app');

app.start(3000);




Start the app


  > npm start






Open up http://localhost/:3000 to see if it's working.



It works!
 




Test the test


Create the test folder


  > mkdir test


Create test file


  > vi test/app.test.js


And place the following into it.


var chai = require('chai')
var assert = chai.assert;


describe('My App', function() {

   
describe('Testing equality', function() {
       
it('1 should equal 1', function () {
           
assert.equal(1, 1);
        });
    });
});




Run the test via npm


  > npm test







TDD


Now that I have express and the test basically running I want to do a little TDD.


Write the test for the next desired feature


What is the next feature I want?

Feature:
/hello should return a 200 status

Update app.test.js to the following


var chai = require('chai')
var assert = chai.assert;
var request = require('superagent');

describe('My App', function() {
   
var myApp = require('../app.js');
   
var port = 3000;
   
var baseUrl = 'http://localhost:' + port

   
before(function(done) {
       
myApp.start(port, done);
    });


    after(
function(done) {
       
myApp.stop(done);
    });


   
describe('When requested at /hello', function () {
       
it('should return 200 code', function (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {
               
assert.equal(res.status, 200);
                done();
            });
        });
    });

});


The before will start the server, the after will shut the server off.
And for the test I am just making sure /hello returns a 200 code.



Run the test


  > npm test



It fails





Implement the Feature


Edit app.js

Add the following function to it.


app.get('/hello', function sendResponse(req,res) {
    res.
status(200).send('Hello World!');
});


Now run the test


Write the test for the next desired feature


What is the next feature I want?

Feature:
/hello should return
a content-type that includes application/json

Update app.test.js adding the following function


describe('When requested at /hello', function () {
    it('should return a content-type with application/json', function (done) {
       
request.get(baseUrl + "/hello").end(function(err, res) {
           
assert.equal(res.headers['content-type'], 'application/json');
            done();
        });
    });
});



Run the test… it fails


Implement the Feature


Edit app.js

Edit the /hello function


app.get('/hello', function sendResponse(req,res) {
    res.
json({"not-msg":42});
});


Now run the test


… It fails






It fails because content-type contains more than just application/json.  Content-type is 'application/json; charset=utf-8'

To check if content-type contains "application/json" update the test code to the following.


describe('When requested at /hello', function () {
    it('should return a content-type with application/json', function (done) {
       
request.get(baseUrl + "/hello").end(function(err, res) {
            assert.include(res.headers['content-type'], 'application/json');
            done();
        });
    });
});



Run the test again

Now it passes





Write the test for the next desired feature


What is the next feature I want?

Feature:
/hello should return
A JSON object that matches a given schema.

I am going to test my JSON against a JSON schema to prove it's valid.

Here is my JSON schema


{
   
"$schema": "http://json-schema.org/draft-04/schema#",
   
"title": "Message Schema v1",
   
"type": "object",
   
"required": ["msg","email"],
   
"additionalProperties": false,
   
"properties": {
       
"msg": {
           
"type": "string",
           
"pattern": "^Hello.*$"
       
},
       
"email": {
           
"type": "string",
           
"format": "email"
       
}
    }
}



This schema requires two properties "msg" and "email". 


   
"required": ["msg","email"],


It does not allow any other properties


    "additionalProperties": false,


"msg" is a String that starts with "Hello"


       
"msg": {
           
"type": "string",
           
"pattern": "^Hello.*$"
       
},


"email" is a String that is formatted as an email according to RFC 5322, section 3.4.1.  see http://tools.ietf.org/html/rfc5322 [4]



To test it out go to http://jsonschemalint.com/draft4/# [5] and paste in the schema and fiddle around with the message to get a valid version.



To create a test for this I need a chai plugin called chai-json-schema… Actually you need chai2-json-schema.. the original project has not been updated to work with chai 2.x.

 Install cha2-json-schema


  > npm install --save-dev chai2-json-schema




Update app.test.js adding a test for the schema.


var chai = require('chai')
var assert = chai.assert;
var request = require('superagent');
chai.use(require('chai2-json-schema'));

describe('My App', function() {
   
var myApp = require('../app.js');
   
var port = 3000;
   
var baseUrl = 'http://localhost:' + port
   
var msgSchema = {
       
"$schema": "http://json-schema.org/draft-04/schema#",
       
"title": "Message Schema v1",
       
"type": "object",
       
"required": ["msg","email"],
       
"additionalProperties": false,
       
"properties": {
           
"msg": {
               
"type": "string",
               
"pattern": "^Hello.*$"
           
},
           
"email": {
               
"type": "string",
               
"format": "email"
           
}
        }
    };



    before(
function(done) {
       
myApp.start(port, done);
    });

    after(
function(done) {
       
myApp.stop(done);
    });

   
describe('When requested at /hello', function () {
       
it('should return 200 code', function (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {
               
assert.equal(res.status, 200);
                done();
            });
        });
    });

   
describe('When requested at /hello', function () {
        it('should return a content-type with application/json', function (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {
               
assert.include(res.headers['content-type'], 'application/json');
                done();
            });
        });
    });

   
describe('When requested at /hello', function () {
        it('should return a JSON object that matches the msgSchema', function (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {
               
assert.jsonSchema(res.body, msgSchema);
                done();
            });
        });
    });


});


Run the test



It fails




Implement the Feature


Edit app.js

Edit the /hello function with a schema valid JSON object.


app.get('/hello', function sendResponse(req,res) {
    res.json({"msg":"Hello World!", "email": "me@example.com"});
});


Now run the test


… It fails




OK it passes.

Run the app.


  > npm start





Looks good J






git


I am going to …
·         create a local git repo
·         add MyApp to the repo
·         create a remote repo on a git server I have
·         Push my app up to this git server

After I get all that done I will figure out how to push the app up to Heroku and get it running.


  > git init .




Edit the .gitignore


  > vi .gitignore


Here is my .gitignore file (Based on these .gitignore files I found https://github.com/github/gitignore/blob/master/Node.gitignore [6]  https://github.com/github/gitignore/tree/master/Global [7]




 #Intellij
*.iml
.idea/

# Linux
.directory
*~

# OS X
.DS_Store*
Icon?
._*

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# npm
node_modules
logs
coverage
*.log
*.gz
.grunt




I think it's always a good idea to run git status before adding anything to your git repo to see what is going to be added (sometimes I screw up our .gitignore file or some odd file sneaks in there)


  > git status




Looks good, it's not adding the node_modules folder, but it is the test folder.




Do an initial commit.


  > git add --all
  > git status
  > git commit  -m "Initial commit"





git push to remote server


I have my own git server at home.  I am going to create a bare repo in it, add it as a remote to this repo and push it up.

From my git server create a bare repo.


  > git --bare init myapp.git






Now back to your local machine ….

Add the remote git repo to push to.  (adjust this command to your needs J)


  > git remote add origin git@git.example.com:/git/myapp.git




I am using git 1.9 and I have to set push defaults


  > git config --global push.default simple


Push and set the upstream to origin master.


  > git push --set-upstream origin master






I always like to make sure I can pull this back down again.


  > cd /tmp
  > git clone git@git.example.com:/git/myapp.git




Looks good J




Test to make sure it runs


  > cd myapp
  > npm install
  > npm start









Back to Heroku


Now finally back to Heroku…

I have a working Express App and I want to push it up to Heroku.  How do I do this?  Can I do it all via the command line.

Looking real quick at my Heroku dashboard.



I have no apps running.


From the command line use heroku to test what apps you have.


  > heroku apps




No apps running, hey look an update is available to the toolbelt.

Update the toolbelt (if yours is out of date).


  > heroku update




Looks like that does not work….

Run this command


  > wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh





Looks good now J

Poking around I found this page https://devcenter.heroku.com/articles/deploying-nodejs [8]

Looks like I need to add the "engines" field to package.json, to tell Heroku which version of node to use.



  > vi package.json




Here is my updated  package.json


{
  "name": "MyApp",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "private": "true",
  "scripts": {
    "test": "mocha --recursive test"
  },
  "engines" : {
    "node" : "0.12.x"
  },
  "author": "Patrick Bailey",
  "license": "ISC",
  "dependencies": {
    "express": "^4.12.3"
  },
  "devDependencies": {
    "chai": "^2.2.0",
    "chai2-json-schema": "^1.2.0",
    "mocha": "^2.2.4",
    "superagent": "^1.1.0"
  }
}





Add it to the git repo and push it up


  > git status
  > git add --all
  > git commit -m "Added engines to application.json"
  > git push






Try to run it locally using Heroku's foreman app


  > foreman start




Ooops I need a Procfile!  …. Oh wait I don't need one??

From Heroku

Specifying a start script

If you define scripts.start in your package.json file, you don’t need to manually create a Procfile because it will be created automatically. For more information, see Best Practices for Node.js Development: Specify a start script and Heroku Node.js Support.



I do have a "default" start script in package.json… Maybe foreman needs this to be explicit?

I updated my package.json to the following.



{
  "name": "MyApp",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "private": "true",
  "scripts": {
    "test": "mocha --recursive test",
    "start": "node server.js"
  },
  "engines" : {
    "node" : "0.12.x"
  },
  "author": "Patrick Bailey",
  "license": "ISC",
  "dependencies": {
    "express": "^4.12.3"
  },
  "devDependencies": {
    "chai": "^2.2.0",
    "chai2-json-schema": "^1.2.0",
    "mocha": "^2.2.4",
    "superagent": "^1.1.0"
  }
}



Now use forman to run it.


  > foreman start




Same result…

Maybe it needs the Procfile locally to run foreman.  But it does not need it to run when you push it up to Heroku?

Let me try that.


  > vi Procfile


And place the following in it.


web: node server.js




Now use forman to run it.


  > foreman start




Now it works



And the web app works locally.


If I don't want this file added to my git repo I need to add it to the .gitignore file.

As a test check the status


  > git status




There is the Procfile I don't want.


Edit .gitignore


  > git .gitignore


Adding


# Heroku
Procfile


Check the git status


  > git status




Looks good.


Let me add it, commit and push it to my git server


  > git add --all
  > git commit -m "Ignoring Procfile"
  > git push








Login to Heroku


Login to Heroku, from the command line


  > heroku login






Create an app


Run the following to create an app


  > heroku create




Looks like the app has been created

At  https://thawing-castle-3350.herokuapp.com/ and it already created an empty repo for me at Heroku.






Looking at my Heroku admin interface I can see there is an app there.




I see that I have an empty app.

Let me check on my git remotes


  > git remote -v




Looks like it added the Heroku remote, nice!





Push it up


Looks like all I need to do is to push it up.


  > git push heroku master







I get an error?



Let me look at the log files


  > heroku logs --tail





Redhotvengeance had the answer

Heroku dynamically assigns your app a port, so you can't set the port to a fixed number. Heroku adds the port to the env, so you can pull it from there. Switch your listen to this:

.listen(process.env.PORT || 5000)

That way it'll still listen to port 5000 when you test locally, but it will also work on Heroku.


OK, so I can't set a static port!

Let me edit server.js to the following


var app = require('./app');

app.start(process.env.PORT || 3000);


Let me add it, commit and push it to my git server and then to the Heroku server.


  > git add --all
  > git commit -m "Added process.env.Port for Heroku"
  > git push
  > git push heroku master














Wahoo that's working.


Fiddling around


Let me try this out again…

From the command line remove the app (destroy.. Hulk Smash!!)

First list the apps


  > heroku apps







  > heroku apps:destroy --app thawing-castle-3350




Nice, it has a double check.  You need to type in the app name to destroy it.


  > heroku apps









Now that I know…


Now that I know what I am doing, let me go to another server, clone my app from my git repo, create an app in Heroku and try to push it up.


  > git clone git@git.example.com:/git/myapp.git
  > cd myapp






Install the Heroku toolbelt. (It's a new machine)


  > wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh



Login to Heroku, from the command line


  > heroku login





Run the following to create an app


  > heroku create




Let me check on my git remotes


  > git remote -v | grep heroku




Looks good J


Push it to Heroku


  > git push heroku master




Open the web app in this case



OK I see now !   Not a bad deploy process Kudos to the folks at Heroku!







References

[1]        devDependencies npm documentation
                Accessed 4/2015
[2]        How do you install “development only” NPM modules for Node.js (package.json)?
                Accessed 4/2015
[3]        Running scripts with npm
                Accessed 4/2015
[4]        Internet Message Format
                Accessed 4/2015
[5]        JSON schema Lint
                Accessed 4/2015
[6]        nodejs .gitignore file
                Accessed 4/2015
[7]        global .gitignore files.
                Accessed 4/2015
[8]        Deploying Node.js Apps on Heroku
                Accessed 4/2015
[9]        Heroku + node.js error (Web process failed to bind to $PORT within 60 seconds of launch)
                Accessed 4/2015


No comments:

Post a Comment