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…
I think I found the answer here https://docs.npmjs.com/files/package.json#devdependencies
[1]
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… ?
I found the answer to that here http://stackoverflow.com/questions/9268259/how-do-you-install-development-only-npm-modules-for-node-js-package-json
[2]
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
|
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
|
And open http://localhost:3000/hello
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
|
Now if I open https://thawing-castle-3350.herokuapp.com/
I get an error?
Let me look at the log files
> heroku logs --tail
|
I found this post on the error http://stackoverflow.com/questions/15693192/heroku-node-js-error-web-process-failed-to-bind-to-port-within-60-seconds-of
[9]
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