Objectives

Deploy the application + the mongo database, to an application server.

Lab 9 Exercise Solutions

app/views/login.hbs

        </form>
        {{> formerror }}
      </div>

app/controllers/accounts.js

exports.authenticate = {

  auth: false,

  validate: {

    payload: {
      email: Joi.string().email().required(),
      password: Joi.string().required(),
    },

    options: {
      abortEarly: false,
    },

    failAction: function (request, reply, source, error) {
      reply.view('login', {
        title: 'Sign in error',
        errors: error.data.details,
      }).code(400);
    },

  },

  handler: function (request, reply) {
    const user = request.payload;
    User.findOne({ email: user.email }).then(foundUser => {
      if (foundUser && foundUser.password === user.password) {
        request.cookieAuth.set({
          loggedIn: true,
          loggedInUser: user.email,
        });
        reply.redirect('/home');
      } else {
        reply.redirect('/signup');
      }
    }).catch(err => {
      reply.redirect('/');
    });
  },

};

app/views/settings.hbs

        </form>
        {{> formerror }}
      </div>

app/controllers/accounts.js

exports.updateSettings = {

  validate: {

    payload: {
      firstName: Joi.string().required(),
      lastName: Joi.string().required(),
      email: Joi.string().email().required(),
      password: Joi.string().required(),
    },

    options: {
      abortEarly: false,
    },

    failAction: function (request, reply, source, error) {
      reply.view('signup', {
        title: 'Sign up error',
        errors: error.data.details,
      }).code(400);
    },

  },

  handler: function (request, reply) {
    const editedUser = request.payload;
    const loggedInUserEmail = request.auth.credentials.loggedInUser;

    User.findOne({ email: loggedInUserEmail }).then(user => {
      user.firstName = editedUser.firstName;
      user.lastName = editedUser.lastName;
      user.email = editedUser.email;
      user.password = editedUser.password;
      return user.save();
    }).then(user => {
      reply.view('settings', { title: 'Edit Account Settings', user: user });
    }).catch(err => {
      reply.redirect('/');
    });
  },

};

Heroku Node Guide

Visit heroku and create an account:

An installation of git is a prerequisite for working with heroku.

You will also need to install the Heroku CommandLine on your worksation:

Make sure these are installed and functioning before proceeding further.

In order to get familiar with the heroku workflow, work through one of the getting started guides:

In particular, the node.js guide:

Git

Before proceeding, the source for the application will need to be committed to git. You may gave been doing this already. If not, we can commit the project to a local git repository now.

First, make sure you have a file called .gitignore in your project folder:

.gitignore

.idea
node_modules

Now create a git repository in the project:

git init

Check the status is the repository, displaying all files in the project not yet committed to git:

git status -su

This should display this list:

?? .gitignore
?? .jscsrc
?? app/controllers/accounts.js
?? app/controllers/assets.js
?? app/controllers/donations.js
?? app/models/db.js
?? app/models/donation.js
?? app/models/user.js
?? app/views/home.hbs
?? app/views/layout/layout.hbs
?? app/views/login.hbs
?? app/views/main.hbs
?? app/views/partials/donate.hbs
?? app/views/partials/donationlist.hbs
?? app/views/partials/formerror.hbs
?? app/views/partials/mainmenu.hbs
?? app/views/partials/welcomemenu.hbs
?? app/views/report.hbs
?? app/views/settings.hbs
?? app/views/signup.hbs
?? index.js
?? package.json
?? public/images/favicon.png
?? public/images/homer.png
?? public/images/homer2.png
?? public/images/homer3.png
?? public/images/homer4.jpeg
?? public/images/homer5.jpg
?? routes.js

In the above list, ?? indicates that the file is not yet committed to git. We can all files to git now:

git add .

Check the status again:

$ git status -su
A  .gitignore
A  .jscsrc
A  app/controllers/accounts.js
A  app/controllers/assets.js
A  app/controllers/donations.js
A  app/models/db.js
A  app/models/donation.js
A  app/models/user.js
A  app/views/home.hbs
A  app/views/layout/layout.hbs
A  app/views/login.hbs
A  app/views/main.hbs
A  app/views/partials/donate.hbs
A  app/views/partials/donationlist.hbs
A  app/views/partials/formerror.hbs
A  app/views/partials/mainmenu.hbs
A  app/views/partials/welcomemenu.hbs
A  app/views/report.hbs
A  app/views/settings.hbs
A  app/views/signup.hbs
A  index.js
A  package.json
A  public/images/favicon.png
A  public/images/homer.png
A  public/images/homer2.png
A  public/images/homer3.png
A  public/images/homer4.jpeg
A  public/images/homer5.jpg
A  routes.js

'A' means it has been added to the repo.

We can now commit all of these files:

 git commit -m "first commit"

This will respond with this:

[master (root-commit) 12abda7] first commit
 29 files changed, 707 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .jscsrc
 create mode 100644 app/controllers/accounts.js
 create mode 100644 app/controllers/assets.js
 create mode 100644 app/controllers/donations.js
 create mode 100644 app/models/db.js
 create mode 100644 app/models/donation.js
 create mode 100644 app/models/user.js
 create mode 100644 app/views/home.hbs
 create mode 100644 app/views/layout/layout.hbs
 create mode 100644 app/views/login.hbs
 create mode 100644 app/views/main.hbs
 create mode 100644 app/views/partials/donate.hbs
 create mode 100644 app/views/partials/donationlist.hbs
 create mode 100644 app/views/partials/formerror.hbs
 create mode 100644 app/views/partials/mainmenu.hbs
 create mode 100644 app/views/partials/welcomemenu.hbs
 create mode 100644 app/views/report.hbs
 create mode 100644 app/views/settings.hbs
 create mode 100644 app/views/signup.hbs
 create mode 100644 index.js
 create mode 100644 package.json
 create mode 100644 public/images/favicon.png
 create mode 100644 public/images/homer.png
 create mode 100644 public/images/homer2.png
 create mode 100644 public/images/homer3.png
 create mode 100644 public/images/homer4.jpeg
 create mode 100644 public/images/homer5.jpg
 create mode 100644 routes.js

Check the status again:

git status -su

which should respond with a blank message.

MongoLab

Create a free account here:

Once you have successfully logged in, create a new MongoDB deployment called donation:

Inspect the database:

and in the users tab, create a new user for this database. Use 'donationuser' for the username and password.

Note carefully the connection string at the top of the page:

 mongodb://<dbuser>:<dbpassword>@ds052323.mlab.com:1234/donation

You will need to formulate a connection string using the donationuser you have just created. It might look like this:

 mongodb://donationuser:donationuser@ds055626.mlab.com:55626/donation

(not exactly the above as you will have different server/port addresses etc..)

To test this connection string out, look again at our db.js module:

app/models/db.js

...
var dbURI = 'mongodb://localhost/donation';
if (process.env.NODE_ENV === 'production') {
  dbURI = process.env.MONGODB_URI;
}
...

Purely as a temporary measure, change the dbURI initlisation to use your connection string:

var dbURI = 'mongodb://donationuser:donationuser@ds0XXXX.mlab.com:XXXX/donation';

Restart the app, and see if it launches successfully. As you register users / make donations, observe the database on MongoLab. You should see the collections being populated on that service.

If it works, then change it back to what it was, but keep the connection string (as a comment for the moment).

Heroku

Verify that the following command responds with a recent version number:

heroku --version

Now log in to your heroku account:

heroku login
Email: 
Password (typing will be hidden):

Once logged in, create a new application on heroku

heroku create

This will respond with a new name in a few seconds:

Creating app... ⬢ calm-brushlands-29225
https://calm-brushlands-29225.herokuapp.com/ | https://git.heroku.com/calm-brushlands-29225.git

The two urls reported are the domain to access the app on (you will need this in a moment), and also the remote git repository where the source of your app pulled from during deployment.

On the web, you can check to see if the app is present on your dashboard:

Explore the dashboard for the app for a few minutes. We havent deployed it yet however.

Prepare Application for Deployment

Before we deploy the application, we need to incorporate a small change to package.json:

    "start": "node index",

This entry is within the scripts section:

{
  "name": "donation-web",
  "version": "1.0.0",
  "description": "an application to host donations for candidates",
  "main": "index.js",
  "scripts": {
    "start": "node index",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "handlebars": "^4.0.5",
    "hapi": "^14.1.0",
    "hapi-auth-cookie": "^6.1.1",
    "inert": "^4.0.1",
    "joi": "^9.0.4",
    "mongoose": "^4.5.8",
    "vision": "^4.1.0"
  }
}

Having made this small change - we need to commit it. First verify that the file has been modified:

git status -su

which will respond with:

 M package.json

Now commit this change to the repo:

git commit -m "node launch script added"

which should respond as follows:

[master 902e4d6] node launch script added
 1 file changed, 1 deletion(-)

Check that the status returns blank:

git status -su

We can now deploy the app:

git push heroku master

This will report details on the deployment:

Counting objects: 43, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (39/39), done.
Writing objects: 100% (43/43), 506.04 KiB | 0 bytes/s, done.
Total 43 (delta 6), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Node.js app detected
remote:
remote: -----> Creating runtime environment
remote:
remote:        NPM_CONFIG_LOGLEVEL=error
remote:        NPM_CONFIG_PRODUCTION=true
remote:        NODE_ENV=production
remote:        NODE_MODULES_CACHE=true
remote:
remote: -----> Installing binaries
remote:        engines.node (package.json):  unspecified
remote:        engines.npm (package.json):   unspecified (use default)
remote:
remote:        Resolving node version (latest stable) via semver.io...
remote:        Downloading and installing node 5.11.1...
remote:        Using default npm version: 3.8.6
remote:
remote: -----> Restoring cache
remote:        Skipping cache restore (new runtime signature)
remote:
remote: -----> Building dependencies
remote:        Installing node modules (package.json)
remote:        donation-web@1.0.0 /tmp/build_7caabad87a663748ff48ad08cbaccca9
remote:        ├─┬ handlebars@4.0.5
remote:        │ ├── async@1.5.2
remote:        │ ├─┬ optimist@0.6.1
remote:        │ │ ├── minimist@0.0.10
remote:        │ │ └── wordwrap@0.0.3
remote:        │ ├─┬ source-map@0.4.4
remote:        │ │ └── amdefine@1.0.0
remote:        │ └─┬ uglify-js@2.7.0
remote:        │   ├── async@0.2.10
remote:        │   ├── source-map@0.5.6
remote:        │   ├── uglify-to-browserify@1.0.2
remote:        │   └─┬ yargs@3.10.0
remote:        │     ├── camelcase@1.2.1
remote:        │     ├─┬ cliui@2.1.0
remote:        │     │ ├─┬ center-align@0.1.3
remote:        │     │ │ ├─┬ align-text@0.1.4
remote:        │     │ │ │ ├─┬ kind-of@3.0.4
remote:        │     │ │ │ │ └── is-buffer@1.1.4
remote:        │     │ │ │ ├── longest@1.0.1
remote:        │     │ │ │ └── repeat-string@1.5.4
remote:        │     │ │ └── lazy-cache@1.0.4
remote:        │     │ ├── right-align@0.1.3
remote:        │     │ └── wordwrap@0.0.2
remote:        │     ├── decamelize@1.2.0
remote:        │     └── window-size@0.1.0
remote:        ├─┬ hapi@14.1.0
remote:        │ ├── accept@2.1.2
remote:        │ ├── ammo@2.0.2
remote:        │ ├── boom@3.2.2
remote:        │ ├── call@3.0.3
remote:        │ ├── catbox@7.1.2
remote:        │ ├── catbox-memory@2.0.3
remote:        │ ├── cryptiles@3.0.2
remote:        │ ├── heavy@4.0.2
remote:        │ ├── hoek@4.0.2
remote:        │ ├── iron@4.0.2
remote:        │ ├── items@2.1.1
remote:        │ ├── kilt@2.0.2
remote:        │ ├─┬ mimos@3.0.3
remote:        │ │ └── mime-db@1.23.0
remote:        │ ├── peekaboo@2.0.2
remote:        │ ├── shot@3.1.1
remote:        │ ├── statehood@4.0.3
remote:        │ ├─┬ subtext@4.0.5
remote:        │ │ ├── content@3.0.2
remote:        │ │ ├─┬ pez@2.1.2
remote:        │ │ │ ├── b64@3.0.2
remote:        │ │ │ └─┬ nigel@2.0.2
remote:        │ │ │   └── vise@2.0.2
remote:        │ │ └── wreck@8.0.1
remote:        │ └── topo@2.0.2
remote:        ├─┬ hapi-auth-cookie@6.1.1
remote:        │ ├── hoek@3.0.4
remote:        │ └── joi@7.3.0
remote:        ├─┬ inert@4.0.1
remote:        │ ├── joi@8.4.2
remote:        │ └─┬ lru-cache@4.0.1
remote:        │   ├── pseudomap@1.0.2
remote:        │   └── yallist@2.0.0
remote:        ├─┬ joi@9.0.4
remote:        │ ├── isemail@2.2.1
remote:        │ └── moment@2.14.1
remote:        ├─┬ mongoose@4.5.8
remote:        │ ├── bson@0.4.23
remote:        │ ├── hooks-fixed@1.2.0
remote:        │ ├── kareem@1.1.3
remote:        │ ├─┬ mongodb@2.1.18
remote:        │ │ ├── es6-promise@3.0.2
remote:        │ │ ├─┬ mongodb-core@1.3.18
remote:        │ │ │ └─┬ require_optional@1.0.0
remote:        │ │ │   ├── resolve-from@2.0.0
remote:        │ │ │   └── semver@5.3.0
remote:        │ │ └─┬ readable-stream@1.0.31
remote:        │ │   ├── core-util-is@1.0.2
remote:        │ │   ├── inherits@2.0.1
remote:        │ │   ├── isarray@0.0.1
remote:        │ │   └── string_decoder@0.10.31
remote:        │ ├── mpath@0.2.1
remote:        │ ├── mpromise@0.5.5
remote:        │ ├─┬ mquery@1.11.0
remote:        │ │ ├── bluebird@2.10.2
remote:        │ │ ├── debug@2.2.0
remote:        │ │ └── sliced@0.0.5
remote:        │ ├── ms@0.7.1
remote:        │ ├── muri@1.1.0
remote:        │ ├── regexp-clone@0.0.1
remote:        │ └── sliced@1.0.1
remote:        └─┬ vision@4.1.0
remote:        ├── hoek@3.0.4
remote:        └─┬ joi@8.4.2
remote:        └── hoek@4.0.2
remote:
remote:
remote: -----> Caching build
remote:        Clearing previous node cache
remote:        Saving 2 cacheDirectories (default):
remote:        - node_modules
remote:        - bower_components (nothing to cache)
remote:
remote: -----> Build succeeded!
remote:        ├── handlebars@4.0.5
remote:        ├── hapi@14.1.0
remote:        ├── hapi-auth-cookie@6.1.1
remote:        ├── inert@4.0.1
remote:        ├── joi@9.0.4
remote:        ├── mongoose@4.5.8
remote:        └── vision@4.1.0
remote:
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote:        Done: 15.3M
remote: -----> Launching...
remote:        Released v5
remote:        https://agile-depths-49364.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy.... done.
To https://git.heroku.com/agile-depths-49364.git
 * [new branch]      master -> master

Visit the url for the app as reported on the last line, or alternatively just enter the following command:

heroku open

Which will open the default browser on the app.

Production Mode DB URI

Although the app will launch, we will not be able to get beyond the sign up screen. Trying to register a new user will generate an internal error on the app.

It can be challenging to debug these errors without access to the log. From within the project folder, enter the following command:

heroku logs --tail

This will give us a live log in the console. Probing this a little deeper, we see the reason why the app is failing:

2016-08-07T11:45:49.449373+00:00 heroku[web.1]: Starting process with command `npm start`
2016-08-07T11:45:51.420126+00:00 app[web.1]:
2016-08-07T11:45:51.420148+00:00 app[web.1]: > donation-web@1.0.0 start /app
2016-08-07T11:45:51.420149+00:00 app[web.1]: > node index
2016-08-07T11:45:51.420150+00:00 app[web.1]:
2016-08-07T11:45:52.419684+00:00 app[web.1]: Server listening at: http://1bee6dd6-e0a2-4b2a-81fe-9c6e9485bea3:48942
2016-08-07T11:45:52.426600+00:00 app[web.1]: Mongoose disconnected
2016-08-07T11:45:52.427543+00:00 app[web.1]: Mongoose connection error: MongoError: getaddrinfo ENOTFOUND undefined undefined:27017

We dont seem to be connecting to the database correctly.

Looking at db.js, we can see the source of the problem.

var dbURI = 'mongodb://localhost/donation';
if (process.env.NODE_ENV === 'production') {
  dbURI = process.env.MONGOLAB_URI;
}

The app is not connecting to the correct database. To fix this, we need to do two things:

  • (1) Set the app to 'production' mode
  • (2) Set the string MONGOLAB_URI to our database on mongoLab

1: Enable production mode:

On the command line, in your app folder enter:

heroku config:set NODE_ENV="production"

This should respond with:

Setting NODE_ENV and restarting ⬢ agile-depths-49364... done, v4
NODE_ENV: production

2: Setting the MongoLab connection string:

Also on the command line, in your app folder enter:

heroku config:set MONGOLAB_URI=mongodb://donationuser:donationuser@dsXXX.mlab.com:XXXX/donation

(use the string from your app in step 4)

Now restart the app:

heroku restart

We should be able to fully exercise the application now. Keep an eye on the log window if you have it still open.

Connecting Robomongo to the MongoLab (heroku) database

The database we are now using is on mongoLab- and it might be useful to be able to browse directly to it.

We already have the connection string - for example something like this:

mongodb://heroku_4pt2zvkj:omev5e4sctvbiaa0i1t5cbstdj@ds011902.mlab.com:11902/heroku_4pt2zvkj

This encodes the following:

  • address: ds011902.mlab.com
  • port: 11902
  • database: heroku_4pt2zvkj
  • password: omev5e4sctvbiaa0i1t5cbstdj

Extract the corresponding from your connection string and enter them in Robomongo 'New Connection' panels:

If it works - you should be able to browse directly to the remote database:

Exercises

Archive of the project so far:

In order to gain some practice with deployment, make a copy of your project into another folder and deploy it as a separate application on heroku from the app deployed on this lab.

When you have duplicated the app - and before you start this lab again - delete the .git folder from the copy, as this lab assumes you are starting from a project not committed to git.