J Cole Morrison
J Cole Morrison

J Cole Morrison

Lead Engineer @Fieldboom, former Techstars Hackstar, AWS Solutions Architect, and Headmaster at awsdevops.io

Lost in DevOps and Code?
I can help. Let's chat.

Building an Angular and Express App Part 3

Posted by J Cole Morrison on .

Building an Angular and Express App Part 3

Posted by J Cole Morrison on .

This is a part 3 in a series of posts. You can check out the other parts here:

You should go through those to get the most out of this series.

This post is going to cover the following:

  • Getting our local MongoDB hooked up to our node application
  • Setting up MongooseJS to act as our object model
  • Making the signup page active
  • Signing up your users and saving them to Mongo

Note in code examples ... represents a break in the code where older code is. For example:

// Top of our file

... // <-- just represents code in between with no change

module.exports = app;
// Bottom of our file

Hopefully that makes sense. If not, PLEASE ask me, I'm more than happy to clarify!

Get Git Ready

Let's checkout a new Git branch so that we can venture back to this start point if we mess up. Navigate to your root project folder and do the following:

$ git checkout -b angular-express-tut-part3

For those unfamiliar with Git, pretend that our project is a video game. When we do a git commit it's like saving our progress. When we checkout a new branch like we just did, it's like saving our progress in a different save slot. So if we wanted to, we could do a

$ git checkout angular-express-tut-part2

And we'd be right back where we started at the end of part 2. But just in case, we're going to save to a new slot... in this case checkout a new git branch.

Some Project Prep

First, we're going to rearrange our routes. We're going to make them more inline with the "Express" way of turning routes into "mini-applications." In other words, we're just going to get them setup in such a way that prepares our application for scaling better. As it stands, we might wind up with a really long app.js file full of routes and route imports. Open up server/app.js and do the following:

1) Delete the lines where you imported the routes:

/**
 * Route Imports
 */
var signup = require('./routes/signup');

2) Delete the one route we made at the bottom of the file just above the module.exports = app; portion:

/**
 * Routes
 */
app.use('/signup', signup);

3) rename server/routes to server/router

4) add a new directory routes inside of of your server/router folder. So you'll have a new directory that reads as server/router/routes. This is where we'll be keeping all of our individual routes.

5) move your server/router/signup.js into the server/router/routes/ directory. So it will now be server/router/routes/signup.js.

6) move your server/router/users.js into the server/router/routes directory as well.

7) delete everything in your server/router/index.js file and replace it with the following:

/**
 * The Index of Routes
 */

module.exports = function (app) {

    // The signup route
    app.use('/signup', require('./routes/signup'));
}

8) open up your server/app.js file and just above the our //Error Handling and module.exports = app; lines, require our router:

...

/**
 * Routes
 */
var router = require('./router')(app);

// Error Handling
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
});

module.exports = app;

What we're doing here is pulling in our server/router/index.js file and passing it the instance of our Express application. We're then adding the signup route to it which will handle anything related to signup. The signup route is now self contained in its own file.

On the command line, make sure you have one tab open to your client/ folder and one open to your server/ folder. In the client/ command line tab, do a grunt serve and in the server/ command line tab, do a npm test. Navigate to localhost:3000, click on the signup button and fill out the form. Confirm that when you "Signup!" that the server/ command line tab outputs the information that you filled out in the form.

Checkout what's changed with git:

$ git status

"Save your game":

$ git add -A
$ git commit -m "Reoganized the router"

Again, for those unfamiliar with Git, the git add -A is saying that we want to add all of our files (and new ones) and changes we've made to be "potentially saved." When we call git commit we actually save those changes. The -m is just a short hand that you pass a message about what you did. If you want to see all of the things you've done, type:

$ git log

And you'll see the progress thus far.

Getting MongoDB and Mongoose Hooked Up

First off, we're using Mongoose because it let's us sprinkle some "relational" style database elements. Working directly with MongoDB isn't "bad" but you wind up with a lot of boilerplate that's just a waste of time. Don't worry, it's well supported not only by a team of great developers BUT by the MongoDB foundation itself. Navigate to your server/ directory and do the following:

$ npm install mongoose --save

This will add the MongooseJS node module to your project and save it to your package.json file. While you're at it, in the server/ directory also do the following:

$ npm install bcrypt --save

This is what we'll be using to hash and salt our user passwords into the database for security. This a low level module so it may take some time to install/compile. Additionally, I know some people have trouble compiling it for the first time, so if you run into any problems, navigate over the git hub repo and checkout the issues for help. The Bcrypt Repo.

Now we're going to make some new files and directories in the server/ folder.

  • make a new directory server/database/
  • make a new directory server/database/schemas/
  • make a new file server/database/index.js
  • make a new file server/database/schemas/users.js

Just like with our router, we're going to use our server/database/index.js file as our "table of contents" to interface into our database. Before you do anything else, open up a new command line tab in your root project directory and do the following:

$ mongod --dbpath data/db/ --logpath data/logs/mongodb.log --logappend

This will fire up the mongodb database located in your projectfolder/data/db directory and output logs in the projectfolder/data/logs directory. If you don't have this running, Node will freak out when we try to connect to mongo. Now that that's done open up your server/database/index.js and input the following:

/**
 * Our Database Interface
 */
var mongoose = require('mongoose');

// Connections
var developmentDb = 'mongodb://localhost/test';
var productionDb = 'urlToYourProductionMongoDb';
var usedDb;

// If we're in development...
if (process.env.NODE_ENV === 'development') {
    // set our database to the development one
    usedDb = developmentDb;
    // connect to it via mongoose
    mongoose.connect(usedDb);
}

// If we're in production...
if (process.env.NODE_ENV === 'production') {
    // set our database to the development one
    usedDb = productionDb;
    // connect to it via mongoose
    mongoose.connect(usedDb);
}

// get an instance of our connection to our database
var db = mongoose.connection;

// Logs that the connection has successfully been opened
db.on('error', console.error.bind(console, 'connection error:'));
// Open the connection
db.once('open', function callback () {
  console.log('Databsae Connection Successfully Opened at ' + usedDb);
});

All this file is doing is opening up a connection to our local mongodb. Once you have a production one, you'd just fill in the productionDb variable to its URL. When you run npm start it will connect to the productionDb and when you run npm test it will connect to the developmentDb.

Now open up server/database/schemas/users.js and input the following:

/**
 * Our Schema for Users
 */
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var Schema = mongoose.Schema;

// Define the User Schema
var userSchema = new Schema({
    firstname: { type: String, required: true },
    lastname: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    password: { type: String, required: true },
    profile: {} // for extra information you may / may not want
});

// A method that's called every time a user document is saved..
userSchema.pre('save', function (next) {

    var user = this;

    // If the password hasn't been modified, move along...
    if (!user.isModified('password')) {
        return next();
    }

    // generate salt
    bcrypt.genSalt(10, function(err, salt){

        if (err) {
            return next(err);
        }

        // create the hash and store it
        bcrypt.hash(user.password, salt, function(err, hash){
            if (err) {
                return next(err);
            }
            user.password = hash;
            next();
        });
    });
});

// Password verification helper
userSchema.methods.comparePassword = function (triedPassword, cb) {
    bcrypt.compare(triedPassword, this.password, function(err, isMatch) {
        if(err) return cb(err);
        cb(null, isMatch);
    });
};

// The primary user model
var User = mongoose.model('User', userSchema);

module.exports = User;

Finally, open back up server/database/index.js and require our new schema at the top and the export the user at the bottom:

/**
 * Our Database Interface
 */
var mongoose = require('mongoose');
var UserModel = require('./schemas/users');

...

// Open the connection
db.once('open', function callback () {
  console.log('Databsae Connection Successfully Opened at ' + usedDb);
});

exports.users = UserModel;

So what we've done here is provide a nice separation of concerns for our database. When we want to interact with our database, we'll just require our database index and work from it in there via exposed exports. Now that that's setup, let's move on to actually signing up a user.

Signing up a User and Storing Them

Before we do this, we need to install a couple of extra modules on the server side. They're all for convenience and productivity... but they're terribly convenient and productive! So use them! Go to your server command line tab and do the following:

$ npm install cli-color --save
$ npm install moment --save
$ npm install underscore --save

cli-color will let us output colored console logs with absolute ease. moment will allow us to output human readable times with ease. Underscore shouldn't need any introduction.

Note: I use cli-color because it doesn't modify any global things. Feel free to manually do coloring yourself. Although, since V8 javascript is so fast you shouldn't be worrying about a couple of support modules bogging you down the least bit.

Open up your server/router/routes/signup.js file and put the following:

/**
 * This handles the signing up of users
 */
var express = require('express');
var router = express.Router();
var moment = require('moment');
var _ = require('underscore');
var color = require('cli-color');
var db = require('../../database');
var Users = db.users;

// The POST /signup route
router.post('/', function (req, res) {

    // The posted information from the front-end
    var body = req.body;
    // Current time this occurred
    var time = moment().format('MMMM Do YYYY, h:mm:ss a');

    // Check to see if the user already exists
    // using their email address
    Users.findOne({

        'email': body.email

    }, function (err, user) {

        // If there's an error, log it and return to user
        if (err) {

            // Nice log message on your end, so that you can see what happened
            console.log('Couldn\'t create new user at ' + color.red(time) + ' by ' + color.red(body.email) + ' because of: ' + err);

            // send the error
            res.status(500).json({
                'message': 'Internal server error from signing up new user. Please contact support@yourproject.com.'
            });
        }

        // If the user doesn't exist, create one
        if (!user) {
            console.log('Creating a new user at ' + color.green(time) + ' with the email: ' + color.green(body.email));

            // setup the new user
            var newUser = new Users({
                firstname: body.firstname,
                lastname: body.lastname,
                email: body.email,
                password: body.password1
            });

            // save the user to the database
            newUser.save(function (err, savedUser, numberAffected) {

                if (err) {
                    console.log('Problem saving the user ' + color.yellow(body.email) + ' due to ' + err);
                    res.status(500).json({
                        'message': 'Database error trying to sign up.  Please contact support@yourproject.com.'
                    });
                }

                // Log success and send the filtered user back
                console.log('Successfully created new user: ' + color.green(body.email));

                res.status(201).json({
                    'message': 'Successfully created new user',
                    'client': _.omit(savedUser, 'password')
                });

            });
        }

        // If the user already exists...
        if (user) {
            res.status(409).json({
                'message': body.email + ' already exists!'
            });
        }

    });

});

// export the router for usage in our server/router/index.js
module.exports = router;

It's quite the file, but hopefully my code comments help out. Again, feel free to ask. A lot of this file is error checking and logging those errors to your server output. THIS IS IMPORTANT. I know, it's just a tutorial. But seriously, just save yourself some headaches and start doing this now. When you get some user email spamming you about their account not working, it's good to know as much as possible without having to pester them with a ton of questions.

One last quick change in client/app/scripts/controllers/signup.js. Change your .success and .error functions to the following:

...

var request = $http.post('/signup', user);

request.success(function (data) {
    console.log(data); // <-- changed
});

request.error(function (data) {
    console.log(data); // <-- changed
})

Now! Refresh your page at localhost:3000/#/signup and signup! You'll see in your server logs that the new user is being created, and whether or not it was successful.

In your root project folder, run:

$ git add -A
$ git commit -m "The end of part 3"

And your new branch will be updated. If you want to roll back to the beginning just do a:

$ git checkout angular-express-tut-part2

and of course to come back to the present do a:

$ git checkout angular-express-tut-part3

If you're ready to turn your master branch into your part 3 branch do the following:

$ git checkout master
$ git merge --no-ff angular-express-tut-part3

This will make your master branch into your part 3 branch. Now they'll be the same. I'll do this when I know that the feature branch I've been working on is DEFINITE. If you want to checkout a new branch to play around in, just do:

$ git checkout -b my-new-feature

And you're ready to go.

Recap

This simply setup Mongo up to our Node application. We have it now so that we can sign users up and store their passwords safely. We've also structured our app to be scalable in organization for both routes and database models. Finally, we've continued touching on some workflow with Git and app support issues.

I've put up a git repo of what this looks like on my end when complete. It is missing the client/app/bower_components, client/node_modules and the server/node_modules which can be added by simply running bower install and npm install in the client/ folder and npm install in the server/ folder. (This is because git is ignoring those). Here's the repo:

THE REPO

PLEASE feel free to ask me any questions!

Next Time...

We'll be doing security all day.

J Cole Morrison

J Cole Morrison

http://start.jcolemorrison.com

Lead Engineer @Fieldboom, former Techstars Hackstar, AWS Solutions Architect, and Headmaster at awsdevops.io

J Cole Morrison

J Cole Morrison

Lead Engineer @Fieldboom, former Techstars Hackstar, AWS Solutions Architect, and Headmaster at awsdevops.io

Lost in DevOps and Code?
I can help. Let's chat.

Free 10 Part Video Series on Docker and AWS

The Hitchhiker's VIDEO Guide to AWS ECS and Docker The Hitchhiker's VIDEO Guide to AWS ECS and Docker

An Exploration of Deploying Docker based Apps and Services to AWS EC2 Container Service.

Checkout the FREE 10 Part Video Series