A boilerplate project for a complete web application written using Backbone.js & Marionette, node.js & ExpressJS, MongoDB & Mongoose, Handlebars, Grunt.js, Bower, and Browserify!
Important Note: This article gets a ton of traffic, and the information here is a lot to consume. Please note that the article is over a year old now but the information is still relevant. Some things have changed, most definitely version numbers etc. I will be posting a pretty massive update to this article and the code repo in the coming weeks/months.
I created a super basic single page application (SPA) that is a simple Contacts manager. While this app is pretty simple, there’s actually a lot that’s involved. My goal with this article is to cover the entire stack of the application including; back-end, data, front-end, tools and testing. The stack consists of the following core technologies:
- Back-end: node.js and ExpressJS
- Testing: Mocha, Chai, Sinon, Proxyquire
- Data Layer: MongoDB and Mongoose
- Tools: Grunt, Bower and Browserify
- Front-end: Backbone.js and Marionette.js
- Testing: Jasmine, Karma, and PhantomJS
I’ll go into detail on each of the above components comprising the “full” stack. Again, because its such a simple app, not a whole lot of time will be spent covering the bells and whistles of the user interface or how the app works because that’s fairly obvious (nor did I spend a whole lot of time on the actual implementation, as I didn’t want it to distract from the core of the app). Instead, I’ll go into detail covering every aspect of how the app was built and the workflow process using the various tools.
The end goal is to have a code base to act as a boilerplate starter for any new projects down the road. The code should be fully functional and complete, yet easily digestible. Ultimately, after you read this article and have a solid understand of everything under the hood, you should be able to simply clone the repo and start building your app!
What you can expect from the remainder of this article:
- Part 0: Project setup and dependencies
- Part 1: Back-end – A simple node.js and ExpressJS server
- node.js – initial setup
- ExpressJS – routes/controllers
- Handlebars – templates for rendering page views/layouts
- Data layer – Mongoose and MongoDB:
- MongoDB – server itself and how it works in the Express layer
- Mongoose – Schema definitions for data models
- Seeder – How we seed data when the app is run for the first time
- Routes & Controllers – API layer called from the frontend
- Testing with Mocha, Chai, Sinon, and Proxyquire
- Part 2: Development Tools (Grunt, Bower, Browserify, TDD)
- Grunt – Does all the things!
- Initial configuration (load grunt tasks)
- Clean
- Bower installs
- Browserify (vendor, shim dependencies, and app files)
- LESS transpiling
- Handlebars (precompiling / transform via Browserify)
- Concatenation / Minification & Uglification / Copying
- Watchers (for both backend and frontend rebuilds)
- Karma commands/watchers
- jshint
- Concurrent tasks
- Commands breakdown
- init:dev, build:dev, build:prod, tdd, test:client, etc.
- Bower – Manage front-end dependencies for the project bower.json explained
- Karma/Jasmine – basic explanation of tdd tasks vs test:client (single run)
- Grunt – Does all the things!
- Part 3: Front-end – Contacts app with Backbone & Marionette
- Browserify – why we are using Browserify and its benefits
- Backbone / Underscore & Marionette
- Contacts Application – introduction and basic explanation of the app
- Core:
- Marionette
- Connection to the data layer to cache data
- Router and Controller setup
- Start!
- Views:
- Marionette.ItemView (versus Backbone.View)
- Handlebar precompiled templates
- Models / Collections
- Controller calls to the API
- Core:
- TDD with Karma and Jasmine
- Part 4: Deploy with Heroku!
Part 0: Project setup and dependencies
Before we can get started, lets make sure you have the project and can run it. First, you will want a copy of the project itself. The easiest way to download a copy is by cloning the repo locally using Git:
[sourcecode light=”true”]$ git clone git@github.com:jkat98/benm.git[/sourcecode]
If you don’t have Git installed or are unfamiliar with the concept, I strongly urge you to read some getting started articles (I wrote a pretty quick guide that should help!).
Once you have the project cloned to a local directory on your machine, you’ll want to make sure you have a few of the most basic requirements.
Install node.js and npm
If you don’t have node.js (and npm) installed already, do so by going to http://nodejs.org (npm is included by default with a standard node.js installation). Be sure to download the latest stable version (v0.10.24 as of the time of this writing).
Once you’ve installed node.js the easiest way to confirm that its working is by going to a terminal or command prompt and simply typing:
[sourcecode light=”true”]$ node –version[/sourcecode]
Assuming its working, you should simply get an output of “0.10.24” or whatever version you actually have installed.
Install the MongoDB server
Next you’ll want to be sure you have the MongoDB server installed on your machine. This is a pretty simple install, similar to node.js. You can find the appropriate download and installation files by visiting the downloads section of the MongoDB website: http://www.mongodb.org/downloads. Please do read the install/getting started docs as there are a few additional steps beyond installation. (i.e. creating/setting your data dir, etc)
Note: Installing MongoDB isn’t required, but most of the project won’t work unless you make some manual edits to remove the MongoDB/Mongoose stuff.
Install the Grunt CLI
One last tool that the project absolutely requires is the Grunt command line tool, which can be very easily installed via npm:
[sourcecode light=”true”]$ npm install -g grunt-cli[/sourcecode]
Note: doing a -g install with npm will install that package globally, meaning it is now installed on your machine and available everywhere. Without -g, the packages are only installed locally to the specific project or repo you are working with (and are stored in a node_modules directory within the project).
Mac users: a global npm install may require sudo for it to install properly.
Run the app!
So, assuming that all of the above was successful, you’re now ready to boot up the web app and take it for a spin! First you need to install all of the project’s dependencies (explained in more detail in the next section) and use Grunt.js to initialize the project and launch the server:
[sourcecode light=”true”]
$ npm install
$ grunt init:dev
$ grunt server
[/sourcecode]
Again, assuming everything is working correctly, Grunt should report that the server is up. You should have seen a lot of log output including the MongoDB server connecting, and some seed data inserted. Point your browser to http://localhost:3300 and the app should be up and running! Feel free to play around, click a Contact to view details, add a new Contact, etc.
Don’t worry about all the craziness that just happened – we’ll cover all of it!
Part 1: Backend – A simple node.js and ExpressJS server
Our server, which will be running in node.js, like any web server really has 2 main purposes. The first is that it should handle requests from a browser and serve up whatever files are requested. These files typically fall into 2 main categories: static files (JavaScript, CSS, HTML, images, etc.) and dynamic files (server generated HTML from templates). Because our app is mainly a thick front-end client the server side templates aren’t really necessary (as we could have just served a static html file). Support for Handlebars template rendering has been included in this project, however, so that this is a complete code base. Should you decide you want to create and host a more traditional website (i.e. no Backbone single page front-end) you can do so easily using Handlebars templates on the back-end alone.
The second thing our server should do is communicate with some form of data layer to persist data between visitor sessions. (i.e. you interact with the app, close your browser, come back and your work was saved and is presented back to you.) This is where MongoDB comes in – a noSQL database server that basically uses JSON to store all data.
Package.json – everything the project needs:
Before we can get started writing pretty much any server code, we need to make sure everything our server is going to need actually exists in our development environment. The easiest way to do this is to put together a basic package.json file that contains a list of all of the dependencies the server will require in order to function. As you determine what your server will need, and install them with npm, your package.json file will grow to account for these new dependencies.
Lets take a look at the file and explain some of what’s going on here:
[sourcecode language=”javascript”]
{
“name”: “myapp”,
“description”: “Boilerplate web app using node, express, mongodb, backbone, marionette. Tooling includes Grunt, Bower, Browserify, etc.”,
“version”: “0.0.1”,
“author”: “Jason Krol”,
“repository”: {
“type”: “git”,
“url”: “https://github.com/jkat98/benm.git"
},
[/sourcecode]
- The top most portion contains just standard descriptive stuff for the project. Pretty self explanatory.
[sourcecode language=”javascript” firstline=”10″]
“engines”: {
“node”: “0.10.x”
},
[/sourcecode]
- The engines item just lists the necessary runtime needed in order to run this project, obviously this is node.js and its version.
[sourcecode language=”javascript” firstline=”13″]
“scripts”: {
“start”: “node server.js”
},
[/sourcecode]
- Scripts just notes what the “run” command would be to start the project/server/app/whatever.
[sourcecode language=”javascript” firstline=”16″]
“dependencies”: {
“express”: “latest”,
“mongoose”: “~3.8.3”,
“handlebars-runtime”: “~1.0.12”,
“express3-handlebars”: “~0.5.0”,
“MD5”: “~1.2.0”
},
[/sourcecode]
- Dependencies is a list of the absolute minimum required dependencies the project has in order for it to run properly. Without these, the server just won’t run.
[sourcecode language=”javascript” firstline=”20″]
“devDependencies”: {
“bower”: “~1.2.8”,
“grunt”: “~0.4.1”,
“grunt-contrib-concat”: “~0.3.0”,
“grunt-contrib-jshint”: “~0.7.2”,
“grunt-contrib-uglify”: “~0.2.7”,
“grunt-bower-task”: “~0.3.4”,
“grunt-nodemon”: “~0.1.2”,
“karma-script-launcher”: “~0.1.0”,
“karma-chrome-launcher”: “~0.1.1”,
“karma-html2js-preprocessor”: “~0.1.0”,
“karma-firefox-launcher”: “~0.1.2”,
“karma-jasmine”: “~0.1.4”,
“karma-requirejs”: “~0.2.0”,
“karma-coffee-preprocessor”: “~0.1.1”,
“karma-phantomjs-launcher”: “~0.1.1”,
“karma”: “~0.10.8”,
“grunt-contrib-copy”: “~0.4.1”,
“grunt-contrib-clean”: “~0.5.0”,
“browserify”: “2.36.1”,
“grunt-browserify”: “~1.3.0”,
“load-grunt-tasks”: “~0.2.0”,
“time-grunt”: “~0.2.3”,
“grunt-contrib-watch”: “~0.5.3”,
“grunt-concurrent”: “~0.4.2”,
“grunt-karma”: “~0.6.2”,
“grunt-contrib-less”: “~0.8.3”,
“grunt-contrib-handlebars”: “~0.6.0”,
“grunt-contrib-cssmin”: “~0.7.0”,
“hbsfy”: “~1.0.0”,
“grunt-shell-spawn”: “~0.3.0”,
“chai”: “~1.9.0”,
“sinon”: “~1.8.1”,
“sinon-chai”: “~2.5.0”,
“grunt-simple-mocha”: “~0.4.0”,
“proxyquire”: “~0.5.2”
}
}
[/sourcecode]
- DevDependencies is a list of all of the dependencies the project has for the purposes of development. This list is huge because most of it involves plugins for Grunt (that will be explained later). Also included is Bower, Browserify, Karma, etc. Again, a list of everything we need in order to develop against the project but not necessarily required for the project to just run.
So, with that package.json file in place the first step is to do a simple npm install to get all dependencies installed and ready to rock with our server:
[sourcecode light=”true”]$ npm install[/sourcecode]
This might take a few minutes as its downloading a fair amount of modules. Note, you probably did this step already if you completed the steps in Part 0 getting the project up and running.
If you are starting a new project, the easiest way to get started with your package.json file is to execute an npm init, which will take you through a few quick questions and then generate a new package.json file for you:
[sourcecode light=”true”]$ npm init[/sourcecode]
You could just hit return/enter on every prompt and be satisfied with a mainly empty but complete package.json file.
The node.js server with ExpressJS and Mongoose:
Let’s take a look at some pretty basic node.js code that will act as the main server. We’ll let ExpressJS do what it does best and handle a lot of the low-level stuff we just don’t want to worry about. We can then focus on configuration and creating the API routes that our front-end app will talk to.
Most of the directories within the root pertain to our node.js server. These include app, controllers, views, and views/layouts. The client directory is where all of our front-end code will reside and the public folder will be where its served from. Basically anything inside the public or views directories is visible from a browser, everything else is not.
[sourcecode light=”true”]
—- app
—- controllers
—- views
——– layouts
—- public
——– js
——– css
—- client
——– requires
——– spec
——– src
——– styles
——– templates
—- spec
——– app
——– controllers
[/sourcecode]
The core server file that will house the bulk of the node.js code (at least the boot-up code) will reside in a file called server.js. Lets take a look at that:
[sourcecode language=”javascript”]
var express = require(‘express’),
http = require(‘http’),
path = require(‘path’),
routes = require(‘./app/routes’),
exphbs = require(‘express3-handlebars’),
mongoose = require(‘mongoose’),
seeder = require(‘./app/seeder’),
app = express();
[/sourcecode]
Here we are declaring a bunch of modules that are pulled in via node’s require function. Any items in a require() call that do not have ./ or ../ are modules that are being loaded in from the npm packages that were installed or from node.js itself (for example: http is a part of node.js core, express was installed via npm). The requires() that include a ./ or ../ are our own modules, which we will be defining in a bit.
Now that we have a bunch of modules ready to be used, lets look at some basic ExpressJS configuration code that boots up the server:
[sourcecode language=”javascript” firstline=”10″]
app.set(‘port’, process.env.PORT || 3300);
// …
app.use(express.logger(‘dev’));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(express.cookieParser(‘some-secret-value-here’));
app.use(app.router);
app.use(‘/’, express.static(path.join(__dirname, ‘public’)));
// development only
if (‘development’ == app.get(‘env’)) {
app.use(express.errorHandler());
}
[/sourcecode]
This code is pretty standard ExpressJS stuff. Each of the app.use calls is loading some form of ExpressJS middleware. These are basically plugins that handle a lot of stuff we just don’t want to have to worry about! The last app.use() (before the // development only comment) is setting the public folder to be a static folder, which means Express will serve files in that folder as is (and won’t do anything to them other than serve them).
Handlebars templates for serving dynamic HTML pages
[sourcecode language=”javascript” firstline=”11″]
app.set(‘views’, __dirname + ‘/views’);
app.engine(‘handlebars’, exphbs({
defaultLayout: ‘main’,
layoutsDir: app.get(‘views’) + ‘/layouts’
}));
app.set(‘view engine’, ‘handlebars’);
[/sourcecode]
We are initializing our view engine with Handlebars and setting it to point to our views directory. Its common you might see Jade being used instead as its a popular template engine, and I do like Jade (especially its concise HTML syntax which looks like Zen coding). However, I wanted to use Handlebars instead so that the template language was the same on the back-end and front-end. The server side Handlebars templates are stored in the views directory and subsequently the layouts stored in a layouts directory. The syntax to use Handlebars on the back-end is pretty much identical to that of the front-end!
The Data layer with Mongoose and MongoDB:
So far so good. The last thing left to do is include a connection to our database server. The database server we are using for this project is MongoDB – a noSQL JSON based data store. In my opinion, using MongoDB with a full stack JavaScript project like this is a no-brainer so it was the obvious choice for me to use. Your mileage may vary.
Getting started with MongoDB:
If you are unfamiliar with MongoDB, here’s a super quick crash course. Assuming you have MongoDB installed on your machine, you can start the server by simply typing the command mongod at any terminal or command prompt:
[sourcecode light=”true”]$ mongod[/sourcecode]
In another terminal or command prompt, use mongo to enter the MongoDB shell:
[sourcecode light=”true”]$ mongo[/sourcecode]
This will change your prompt as you are now using the MongoDB shell. A few quick commands:
- show dbs – displays a list of available databases on the system.
- use databasename – switch to existing databasename (or create it if it doesn’t already exist)
- db.things.find() – list all records in the “things” collection of the currently active database (set with ‘use’ earlier)
- db.things.insert({ a: 1, b: 2, c: 3}) – insert a new record into the ‘things’ collection with the object provided.
- db.things.find({a : 1}) – return a list of all records that has the property a with a value of ‘1’
[sourcecode language=”javascript” firstline=”31″]
//connect to the db server:
mongoose.connect(‘mongodb://localhost/MyApp’);
mongoose.connection.on(‘open’, function() {
console.log(“Connected to Mongoose…”);
// check if the db is empty, if so seed it with some contacts:
seeder.check();
});
[/sourcecode]
The node.js driver we are using to connect to our MongoDB server is Mongoose. There are a number of node.js drivers available that work with MongoDB, but I like Mongoose because of the schemas that you can use with your models. (Coming from a “Code First” .net development background personally, this was the most direct comparison when I was learning node.) In our server.js file we just include a small chunk of code that connects to the MongoDB server using the Mongoose driver. Note that the connection url to the MongoDB server is “mongodb://localhost/” followed by the name of the database to connect to. The beauty of MongoDB is that if a database doesn’t exist when its attempted to be connected to, one is created automatically!
Once the connection is established, we run a simple seeder check function that basically checks to see if this is the first time we are running the app – if so it throws a few records into the Contacts collection (a.k.a “table”) so the app isn’t completely empty the first time its run. This isn’t necessary, but just makes for a nice initial impression on first use.
Seeder module:
The seeder object is actually a great introduction to modules in node.js. If you go back to the very top of server.js you’ll recall we included a bunch of require() statements. This is where we take modules in node.js, either those that come with node, those we’ve installed via npm, or our very own, and include them as regular JavaScript variables that we can work with later in our code. Lets look at the seeder module and see what its doing:
[sourcecode language=”javascript”]
var mongoose = require(‘mongoose’),
models = require(‘./models’),
md5 = require(‘MD5’);
module.exports = {
check: function() {
// …
[/sourcecode]
Here we include a few requires() again, fewer this time since this is a specific module and only needs to do what it does and depends on only a few things; namely Mongoose, our data models, and MD5. (MD5 is only used to generate hash values that Gravatar uses for their images.) The more important part is the module.exports line – which exports a JavaScript object that basically has 1 property function called ‘check’. Check() will check to see if there are any records in the Contacts collection, if not it will just insert a bunch of sample records to start with.
API Routes and Controllers:
[sourcecode language=”javascript” firstline=”40″]
//routes list:
routes.initialize(app);
//finally boot up the server:
http.createServer(app).listen(app.get(‘port’), function() {
console.log(‘Server up: http://localhost:’ + app.get(‘port’));
});
[/sourcecode]
Finally, now that our server code is all set up and we have our connection to our database server, the last thing to do is get our routes ready and then start the server!
[sourcecode language=”javascript”]
var home = require(‘../controllers/home’),
contacts = require(‘../controllers/contacts’);
module.exports.initialize = function(app) {
app.get(‘/’, home.index);
app.get(‘/api/contacts’, contacts.index);
app.get(‘/api/contacts/:id’, contacts.getById);
app.post(‘/api/contacts’, contacts.add);
app.put(‘/api/contacts’, contacts.update);
};
[/sourcecode]
[sourcecode language=”javascript”]
module.exports = {
index: function(req, res) {
res.render(‘index’);
}
};
[/sourcecode]
We’re using a 1:1 relationship with a router and controller in this project, so if you take a look at the routes.js file in the app/ dir, you’ll see that its pretty basic. It’s literally just defining all of the endpoints for our API layer that the server will respond to when calls are made from the front-end layer. The actual implementation for each router is defined in the controllers module which can be found in controllers/*.js. Again, noting the requires() at the top of the routes.js file, we include our own modules by referencing ../controllers/file (no .js). One thing to note is that our home controller only has an index which returns a rendered Handlebars template (as noted by the res.render(‘index’)). The contacts controller returns pure JSON results – different results depending on the URL being requested.
The most important controller is the contacts controller (controllers/contacts.js). This is the one that the front-end actually talks to to get the initial collection of contacts, retrieve an individual contact’s full details, as well as add new contacts and update existing contacts. Each of the functions in contacts.js is mainly doing Mongoose inserts/updates/gets against the MongoDB server.
Data Models:
The last piece of our server code is our data models. Our data models are objects where the data schema has been defined. If you look at the app/models.js file you will see a few requires() at the top (specifically Mongoose, Schema, and ObjectId – all related to Mongoose). We define a Contact model which is just a new data schema and the properties defined (and of what type). Finally we export an object that contains a property specifically for the Content model. We do it this way so we can define our entire project’s data schemas and return them all via the primary “models” module. Let’s say in addition to Contacts, we also wanted to store a collection of our favorite books. In the models.js file we would declare another var of Book that = new Schema({}) and inside the object define the properties for each Book (i.e. Title, Author, Description, ISBN, etc). Then in the module.exports after the Contact: line we simply put a , and then Book: mongoose.model(‘Book’, Book);. Then everywhere that we’ve already included var models = require(‘./models’) will instantly also have our Book model as well. You don’t have to include all of your models in 1 file/module this way – its just the way I did it for this project (and probably would work well for most projects unless you had a very large amount of models).
[sourcecode language=”javascript”]
var mongoose = require(‘mongoose’),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Contact = new Schema({
email: { type: String },
name: {
first: { type: String },
last: { type: String }
},
phone: { type: String },
gravatar: { type: String }
});
module.exports = {
Contact: mongoose.model(‘Contact’, Contact)
};
[/sourcecode]
Our entire server consists of just 6 small .js files, most of which have very few lines of code. All of that serves up a fully functional web server with a database connection and data CRUD logic!
Testing node with Mocha, Chai, Sinon, and Proxyquire
We are using a few different tools on the server side to handle testing of our node code. Primarily we are using Mocha, combined with a Grunt mocha runner to execute our tests. The tests themselves are written using Chai as the assertion language, Sinon for spies and stubs, and Proxyquire to do a little magic when it comes to the idea of “fakes” in the node world.
To run the tests for the code, execute the following command:
[sourcecode]
$ grunt test:server
[/sourcecode]
This will execute the mocha runner, and all of the tests we have in the spec folder will be run. You should see the output for each test right in your terminal, hopefully with green checkmarks all the way down the list.
The tests are organized in a similar fashion to the app itself, with an app and controllers folder.
To learn more about testing with node.js, checkout my other post that goes into more detail.
Part 2: Development Tools (Bower, Grunt, Browserify)
Now that the server has been covered, and before we can get to the front-end app, I want to go over the suite of tools that were used during the development of this app.
Bower – Front-end dependency management
Bower is a great little tool! Its basically what npm is to node. Using Bower you can quickly and easily install any front-end dependencies required by your project. Typically, in the past, whenever we wanted to use something like jQuery, underscore, Backbone.js, etc. we always went to the libraries website or GitHub repo (many times one in the same), found the download link, saved a copy of the .js file somewhere in our project, and then added a reference to that file in our main layout. With Bower that process is a whole lot easier.
Like most things done with node, Bower depends on its own bower.json file. This is going to be very similar to the existing package.json (explained earlier) file.
[sourcecode language=”javascript”]
{
“name”: “myapp”,
“version”: “0.0.1”,
“private”: true,
“dependencies”: {
“backbone.marionette”: “~1.4.1”,
“jquery”: “~1.10.2”,
“underscore”: “~1.5.2”,
“backbone”: “~1.1.0”
},
“devDependencies”: {
“jasmine”: “~1.3.1”
},
“exportsOverride”: {
“jquery”: {
“js”: “jquery.js”
},
“underscore”: {
“js”: “underscore.js”
},
“backbone”: {
“js”: “backbone.js”
},
“backbone.marionette”: {
“js”: “lib/backbone.marionette.js”
},
“jasmine”: {}
}
}
[/sourcecode]
Both the dependencies and devDependencies sections are defined. With those defined, you could simply execute ‘bower install’, and like ‘npm install’ it would download the files listed that were predefined in the bower.json file (without having to manually install each).
The exportsOverride section simply defines how the files are going to be organized when they are installed from the ‘bower_components’ to the ‘client/requires’ directory. Note that Jasmine has nothing defined which means it will not be copied over during the client requires phase. This is because the clients don’t need to load the Jasmine library when the app is run on the front-end, Jasmine is simply for testing during local development.
If you were starting a fresh project from scratch and wanted to include jQuery into your project simply install it via Bower:
[sourcecode light=”true”]$ bower install jquery[/sourcecode]
While you’re at it – lets assume you want Backbone.js and underscore.js as well:
[sourcecode light=”true”]
$ bower install backbone
$ bower install underscore
[/sourcecode]
Then, all you need to do is add a reference in your main layout file to bower_components/jquery/jquery.js!
Of course, wouldn’t it be nice if it were even easier than that…
Grunt.js – Task runner to do all the things
Grunt.js is pretty much the shit. The sheer volume of plugins available for it is overwhelming. To that end, however, actually understanding and getting started with Grunt can be really confusing if its not clearly defined why you actually need to use it.
Lets take a step back and look at what a typical development workflow could be without any automated task runners (like Grunt) to take care of the bulk of it:
You have all of your frontend files organized in a way that you’re happy with. You may have many many files for many different reasons – Backbone models, views, collections, routers, controllers, etc. On top of that, you’ve decided that you want to use LESS to write your CSS – cause you’re fancy and everyone else is using it. Being a good web developer, you ultimately want your project .js files to all be concatenated into a single file, ideally compressed (and uglified so people can’t make any sense out of your source code and steal your work). Obviously your LESS files shouldn’t be served up to the browser as is – they need to be converted to normal .css files as well. On top of all of that, you practice TDD so you want your tests for your frontend code to run regularly as you make changes to either the application code or the tests themselves. Furthermore, you need to actually start up your node.js server so you can view the app in a local browser – and any time you make changes to the node code, you need to restart the server. Last but not least – all of those tasks are for a single test run of your app. You would have to repeat most of that every time you make any changes.
O_o Thats a lot of stuff to worry about! That’s where Grunt comes in to save the day. Using Grunt you can create an automated “script” that is run every time you edit certain files or make any changes. With this automated process, running something like “grunt server” can do the following:
- Bower install any of your frontend dependencies
- Concatenate all .js files into a single file (including those Bower dependencies)
- Compress (minify) that single .js file and also uglify it
- Transpile LESS files into a single .css file
- Copy those 2 files to a public folder that your app index.html file actually references
- Launch Karma and execute your suite of tests
- Start up your node server
- Do ALL of the above every time any of the appropriate files are modified
- Make a change to any .js file – Concatenate, Compress, and Copy are rerun. Tests are also rerun.
- Make a change to any .LESS file – Transpile and Copy are rerun.
- Make a change to any of your server specific node.js files – restart the node server
- On top of that, you can run some other handy items like jsHint so all of your JavaScript is instantly checked for syntax errors.
So now a typical development work flow would look a little more like:
- Launch “grunt server”.
- Do work.
- Refresh browser.*
*Note: you could event get fancier and use a Live reload plugin and remove that last part!
Pretty awesome huh?! All the countless b.s. you worry about is thrown right out the window and forgotten about, all so making a website can get out of the way of making a website!
Let’s take a look at the Gruntfile.js we created for this project. Warning – its pretty big so we’ll tackle it in chunks.
[sourcecode language=”javascript”]
module.exports = function(grunt) {
require(‘time-grunt’)(grunt);
require(‘load-grunt-tasks’)(grunt);
grunt.initConfig({
pkg: grunt.file.readJSON(‘package.json’),
[/sourcecode]
This is just the most basic part of the Gruntfile. It basically just uses node’s module.exports to return a Grunt function, which you then launch using initConfig() passing in configuration object. The first 2 items are additional requires for 2 Grunt plugins that are very handy. The more important one is the load-grunt-tasks. Typically with a Gruntfile you need to manually specify all of the tasks that you are going to be using a plugin for. This handy little plugin automates that by reading your package.json file and looking for any plugins in the dependencies and devDependencies list that start with “grunt-” and loads them automatically! That gets a big part of the config work out of the way.
Next up we run Grunt’s initConfig function and configure our entire script. The very first line we read in the main package.json file for user a little later – most specifically we’ll be taking the name of our project from the package file and using that to name some of the files that we create during our build process.
Bower install
Read the bower.json file and install any front-end dependencies.
[sourcecode language=”javascript” firstline=”9″]
bower: {
install: {
options: {
targetDir: ‘client/requires’,
layout: ‘byComponent’
}
}
},
[/sourcecode]
Clean
Any temporary build folders we maintain should be cleaned before each build (so theres no leftover files lying around). The ‘build’ command cleans the entire ‘build’ directory. The dev command cleans a specific list of files (i.e. doesn’t delete the vendor.js file since that typically never changes). Finally, the ‘prod’ command will clean out a directory called ‘dist’ which is what we would use to distribute our app when its ready for release. (This directory would typically only contain 2 key files, myapp.js and myapp.css – plus any necessary images.)
[sourcecode language=”javascript” firstline=”18″]
clean: {
build: [‘build’],
dev: {
src: [‘build/app.js’, ‘build/<%= pkg.name %>.css’, ‘build/<%= pkg.name %>.js’]
},
prod: [‘dist’]
},
[/sourcecode]
Browserify
This is a biggie – and not to be taken lightly. Browserify is a pretty amazing tool, which basically allows you to use modules in your front-end code in the exact same way that you do in node.js on the back-end. What this means is that you can use the exact same coding habits and styles in the full stack. Browserify allows you to build your front-end code in a modular style, and only pull in dependencies in areas of your app as you need them. On top of that, the Browserify Grunt plugin will merge all of the app and vendor files together into a single respective file for each. (So if you had 5 vendors and 20 app files, you would wind up with only a single vendor.js file and a single app.js file.)
The browserify config block is broken into 3 main sections; vendor, app, and test. The vendor file will take all of the frontend dependencies defined from Bower and Browserify them and then merge them into a single vendor.js file. App will do the same for your core app files. Notice that with the app section, we only define main.js (and not an array of all of our .js files). This is because the way Browserify works, it will basically crawl your entire app and everywhere it finds require() functions will load in those dependencies as needed. node.js works pretty much the same way.
Additionally you can shim vendors, defining the dependencies that they themselves have. So, for example, with Marionette you can define that it depends on jQuery, Backbone and underscore. This way, in your frontend app .js files, you need only require(‘backbone.marionette’) and it will automatically include jQuery, underscore, and Backbone without you having to manually require those as well!
Finally, Browserify will also find all requires() that specifically point to front-end Handlebars templates and automatically precompile them into JavaScript functions. This will speed up the front-end rendering process significantly for users since the view templates aren’t being rendered during runtime (which is how Handlebars typically works).
[sourcecode language=”javascript” firstline=”26″]
browserify: {
vendor: {
src: [‘client/requires/**/*.js’],
dest: ‘build/vendor.js’,
options: {
shim: {
jquery: {
path: ‘client/requires/jquery/js/jquery.js’,
exports: ‘$’
},
underscore: {
path: ‘client/requires/underscore/js/underscore.js’,
exports: ‘_’
},
backbone: {
path: ‘client/requires/backbone/js/backbone.js’,
exports: ‘Backbone’,
depends: {
underscore: ‘underscore’
}
},
‘backbone.marionette’: {
path: ‘client/requires/backbone.marionette/js/backbone.marionette.js’,
exports: ‘Marionette’,
depends: {
jquery: ‘$’,
backbone: ‘Backbone’,
underscore: ‘_’
}
}
}
}
},
app: {
files: {
‘build/app.js’: [‘client/src/main.js’]
},
options: {
transform: [‘hbsfy’],
external: [‘jquery’, ‘underscore’, ‘backbone’, ‘backbone.marionette’]
}
},
test: {
files: {
‘build/tests.js’: [
‘client/spec/**/*.test.js’
]
},
options: {
transform: [‘hbsfy’],
external: [‘jquery’, ‘underscore’, ‘backbone’, ‘backbone.marionette’]
}
}
},
[/sourcecode]
LESS
Transpile any .less files to .css and put the .css file in the build directory named ‘myapp’.css. Not only should this file consist of all .less files, but also any other .css files that exist and are required by our vendor dependencies (jQueryUI, reset.css, etc).
[sourcecode language=”javascript” firstline=”81″]
less: {
transpile: {
files: {
‘build/<%= pkg.name %>.css’: [
‘client/styles/reset.css’,
‘client/requires/*/css/*’,
‘client/styles/less/main.less’
]
}
}
},
[/sourcecode]
Concat
Concatenate all of the .js files into a single file. Take the vendor.js file and app.js file (both created by Browserify) and merge them into a single file named ‘myapp’.js. Put this final single file in the build directory.
[sourcecode language=”javascript” firstline=”93″]
concat: {
‘build/<%= pkg.name %>.js’: [‘build/vendor.js’, ‘build/app.js’]
},
[/sourcecode]
Copy
When we are running our Grunt server in dev mode, copy the files from the build directory to the destination that they must reside so that our front-end app can see them and our server can serve them. A different directory for .js files, .css files, and image files. (Take a look at the main.handlebars layout file in server/views/layout/ and you will see where the final .js and .css files are included into the layout.)
[sourcecode language=”javascript” firstline=”97″]
copy: {
dev: {
files: [{
src: ‘build/<%= pkg.name %>.js’,
dest: ‘server/public/js/<%= pkg.name %>.js’
}, {
src: ‘build/<%= pkg.name %>.css’,
dest: ‘server/public/css/<%= pkg.name %>.css’
}, {
src: ‘client/img/*’,
dest: ‘server/public/img/’
}]
},
prod: {
files: [{
src: [‘client/img/*’],
dest: ‘dist/img/’
}]
}
},
[/sourcecode]
cssmin
Remove all white space from our .css file and make it as compressed as possible. By default this is only set for production runs of our app (since during development, it might be nice to still be able to read the final .css file if you inspect it by viewing the source in a browser).
[sourcecode language=”javascript” firstline=”119″]
cssmin: {
minify: {
src: [‘build/<%= pkg.name %>.css’],
dest: ‘dist/css/<%= pkg.name %>.css’
}
},
[/sourcecode]
uglify
Do the same for our main .js file, remove any white space and trim variable names and comments. Make the file as small as possible while still functional. Again, this only occurs during a production run (as during development, while debugging in the browser etc., we still want to be able to read the .js file).
[sourcecode language=”javascript” firstline=”127″]
uglify: {
compile: {
options: {
compress: true,
verbose: true
},
files: [{
src: ‘build/<%= pkg.name %>.js’,
dest: ‘dist/js/<%= pkg.name %>.js’
}]
}
},
[/sourcecode]
watch
Watch all files for any time they get modified. When they do change, execute pre-existing Grunt tasks already defined (most explained above). Watched client/src/*.js files will rerun the Browserify tasks, watched .less files will rerun the transpile step, then all of the necessary copies and repeat every time a file is modified.
[sourcecode language=”javascript” firstline=”141″]
watch: {
scripts: {
files: [‘client/templates/*.hbs’, ‘client/src/**/*.js’],
tasks: [‘clean:dev’, ‘browserify:app’, ‘concat’, ‘copy:dev’]
},
less: {
files: [‘client/styles/**/*.less’],
tasks: [‘less:transpile’, ‘copy:dev’]
},
test: {
files: [‘build/app.js’, ‘client/spec/**/*.test.js’],
tasks: [‘browserify:test’]
},
karma: {
files: [‘build/tests.js’],
tasks: [‘jshint:test’, ‘karma:watcher:run’]
}
},
[/sourcecode]
nodemon
The same as watch, except for the server related .js files. Whenever a node.js file is changed on the server, restart the server so the latest version is running.
[sourcecode language=”javascript” firstline=”161″]
nodemon: {
dev: {
options: {
file: ‘server/server.js’,
nodeArgs: [‘–debug’],
watchedFolders: [‘server/controllers’, ‘server/app’],
env: {
PORT: ‘3300’
}
}
}
},
[/sourcecode]
shell
This is just a simple command line execution. Specifically, launch the ‘mongod’ server command whenever the main server starts (since they must both be running for the app to work properly).
[sourcecode language=”javascript” firstline=”175″]
shell: {
mongo: {
command: ‘mongod’,
options: {
async: true
}
}
},
[/sourcecode]
concurrent
Concurrent means that you can execute a number of “blocking” tasks asynchronously at the same time. For dev the tasks that are run are the nodemon watcher for the server, mongod server for the database, and watcher for the front-end scripts, less, and tests. Without concurrent, typically only one of these would be able to run before it sits and waits and causes Grunt to hang and be blocked before it could execute all of the other necessary tasks that must all run parallel to each other simultaneously.
[sourcecode language=”javascript” firstline=”184″]
concurrent: {
dev: {
tasks: [‘nodemon:dev’, ‘shell:mongo’, ‘watch:scripts’, ‘watch:less’, ‘watch:test’],
options: {
logConcurrentOutput: true
}
},
test: {
tasks: [‘watch:karma’],
options: {
logConcurrentOutput: true
}
}
},
[/sourcecode]
karma
Tasks specific to running the Karma test runner and watcher. We’ll touch more on this in a little bit.
[sourcecode language=”javascript” firstline=”200″]
karma: {
options: {
configFile: ‘karma.conf.js’
},
watcher: {
background: true,
singleRun: false
},
test: {
singleRun: true
}
},
[/sourcecode]
jsHint
Run jsHint syntax checking on all necessary .js files.
[sourcecode language=”javascript” firstline=”213″]
jshint: {
all: [‘Gruntfile.js’, ‘client/src/*/*.js’, ‘client/spec/*/*.js’],
dev: [‘client/src/**/*.js’],
test: [‘client/spec/**/*.js’]
}
[/sourcecode]
And that concludes the initConfig block for our Gruntfile.js! Thats a lot, and typically it could get a whole lot longer as you automate all the things and make your life easier. The amount of time you spend configuring your Gruntfile in the beginning will save you 10 fold during the lifetime of your project development. Not to mention freeing up head space to not have to worry about all that stuff.
Now that all of that is out of the way, we need to setup some simple command line commands that you can execute when starting Grunt to actually do all of this stuff!
[sourcecode language=”javascript” firstline=”220″ highlight=”225″]
grunt.registerTask(‘init:dev’, [‘clean’, ‘bower’, ‘browserify:vendor’]);
grunt.registerTask(‘build:dev’, [‘clean:dev’, ‘browserify:app’, ‘browserify:test’, ‘jshint:dev’, ‘less:transpile’, ‘concat’, ‘copy:dev’]);
grunt.registerTask(‘build:prod’, [‘clean:prod’, ‘browserify:vendor’, ‘browserify:app’, ‘jshint:all’, ‘less:transpile’, ‘concat’, ‘cssmin’, ‘uglify’, ‘copy:prod’]);
grunt.registerTask(‘server’, [‘build:dev’, ‘concurrent:dev’]);
grunt.registerTask(‘test:client’, [‘karma:test’]);
grunt.registerTask(‘tdd’, [‘karma:watcher:start’, ‘concurrent:test’]);
[/sourcecode]
Each tasks ‘register’ is pretty self explanatory, but basically you register a task by first giving it a name, which you execute by:
[sourcecode light=”true”]$ grunt mytask[/sourcecode]
Where ‘mytask’ can be anything you want. Following the name of your task in the registerTask() is an array of tasks you want executed, all defined in the ‘initConfig’ earlier.
init:dev – the task that is executed the first time you want to start working with the project. This will do a Bower install, copy those files to the client_requires folder, and run Browserify for the vendor file. The reason this only needs to execute once at the beginning of development is that your Bower dependencies typically won’t change all that much, so there’s no reason to do a Bower install every single time you make changes to your app files.
build:dev – this does most of the work. This is executed every time you run grunt server. This handles the tasks that are repeated every time you make a change and need to rebuild all of your files.
grunt server – the brains of the operation. This is the command you will execute the most often. It does a build:dev, and then concurrent:dev which launches all of the watchers and servers.
Finally test:client and tdd both launch Karma and run the tests, the only difference is that ‘test:client’ runs once, and ‘tdd’ will run Karma in auto watch mode – rerunning the tests any time the app.js or tests.js files are modified.
TDD with Jasmine, Karma, and PhantomJS
I’ve touched a bit here and there on Karma and TDD in this article so far, but haven’t really explained any of it. That’s because I’ve already written a pretty thorough article covering that in greater detail which you can read here! Suffice it to say that TDD (Test Driven Development) is fairly important. The basic idea behind it is that you have a suite of tests written that check against your actual code. Should you make tweaks or changes to your core code which inadvertently breaks something, your tests will alert you immediately! If you’re not doing TDD regularly, I strongly suggest you consider looking into it and at least giving my other article a read through.
Part 3: Front-end app with Backbone.js & Marionette.js
Now its time to go into detail covering the build of the actual front-end app. The app itself is a very rudimentary and basic app that manages a list of Contacts. The app presents a collection of contacts as small “cards” with a name, email, and Gravatar image. Click a card to get a full details view for the contact. You can also add New contacts and update or delete existing contacts. Its no “To Do” app, but it gets the job done!
As covered earlier in the Gruntfile.js configuration section, we are relying heavily on Browserify for the front-end portion of the app. This is simply because I really like the way Browserify works and I love that it makes coding on the front-end feel exactly like coding on the back-end. Previously I wrote an article discussing using Backbone.js with require.js – but for the purposes of this project I decided to change it up and try something different. Turns out I personally like working with Browserify a lot more than my initial experience with require.js. That being said, if you’ve never worked with node.js before or any other kind of modular front-end development framework, it can all seem pretty confusing. I suggest you give my earlier article a read as the topics and concepts covered translate pretty much the same between require.js and Browserify.
Main.js and App.js – boot up and brains
[sourcecode language=”javascript”]
var App = require(‘./app’);
var myapp = new App();
myapp.start();
[/sourcecode]
Main.js is exactly what it sounds like – the main JavaScript file. This file is small, but it boots everything up! The very first thing it does is, using Browserify, require our main app object (located in app.js). With that object we can create a new instance and officially start the app.
app.js is also exactly what it sounds like – its basically our entire app, well sort of. Its really the brains that coordinates all of the moving parts of the app from this central location. Lets break down app.js and see what its really doing.
[sourcecode language=”javascript”]
var Marionette = require(‘backbone.marionette’),
Controller = require(‘./controller’),
Router = require(‘./router’),
ContactModel = require(‘./models/contact’),
ContactsCollection = require(‘./collections/contacts’);
[/sourcecode]
Looks pretty familiar right? Here we are declaring the dependencies for our main app.js file and stating that we are going to be working with Marionette, our Controller, Router, Contact model, and Contacts collection. Each of these modules (except Marionette) are our own files which will be explained in a bit. Note that Marionette depends on jQuery, underscore and Backbone but we didn’t need to require any of those because they are done so via the dependencies shim defined in the Browserify config in our Gruntfile.js.
Next an empty function is created and exported via the module system. Even though its empty, that function object is then immediately prototyped with a function called “start” that does all the work.
[sourcecode language=”javascript” firstline=”7″]
module.exports = App = function App() {};
App.prototype.start = function(){
App.core = new Marionette.Application();
[/sourcecode]
Inside App.start() four important things occur:
- Create a new Marionette.Application()
- bind an event to initialize:before
- bind an event to app:start
- Start the Marionette app!
The 2 events that are bound to are not actually triggered until the Marionette app starts (which is why App.core.start() is called last). Lets look at each of those events now:
initialize:before
[sourcecode language=”javascript” firstline=”12″]
App.core.on(“initialize:before”, function (options) {
App.core.vent.trigger(‘app:log’, ‘App: Initializing’);
App.views = {};
App.data = {};
// load up some initial data:
var contacts = new ContactsCollection();
contacts.fetch({
success: function() {
App.data.contacts = contacts;
App.core.vent.trigger(‘app:start’);
}
});
});
[/sourcecode]
This event will occur first, immediately before the app is actually started. You can refer to the Marionette documentation on what events are available and the order in which they execute. Here initialize:before happens before app:start – so we define what we want to happen before our app starts. Specifically we create a few cache objects on the App itself (views and data), and fetch our contacts data from the server. Once the fetch is complete, we actually trigger app:start.
app:start
[sourcecode language=”javascript” firstline=”28″]
App.core.vent.bind(‘app:start’, function(options){
App.core.vent.trigger(‘app:log’, ‘App: Starting’);
if (Backbone.history) {
App.controller = new Controller();
App.router = new Router({ controller: App.controller });
App.core.vent.trigger(‘app:log’, ‘App: Backbone.history starting’);
Backbone.history.start();
}
//new up and views and render for base app here…
App.core.vent.trigger(‘app:log’, ‘App: Done starting and running!’);
});
// …
App.core.start();
[/sourcecode]
Inside app:start, basically we create a new instance of the app’s controller and a new instance of our router, and the router takes the controller as a part of its constructor. Both of these are Marionette objects (explained in a bit). In this project, our front-end router and controller work almost exactly the same way as their counterparts on the back-end with the node.js router and controller. The only difference is that the node.js router manages actual URLs visitors can hit on the server – and the Marionette router manages the routes in the URL that can be hit while the front-end app is running. Think of Backbone or Marionette routes as being something like a DOM event but the DOM element is the window.location bar. If the URL in the Browser’s address bar changes, trigger a route event in the app. (Not actually try to load a new page like a normal URL.)
Marionette Router and Controller
As you saw in app.js, we defined new instances of a Router and Controller in the app:start event. I also mentioned that they work almost exactly the same way as the node.js counterparts.
router.js
[sourcecode language=”javascript”]
var Marionette = require(‘backbone.marionette’);
module.exports = Router = Marionette.AppRouter.extend({
appRoutes: {
‘#’ : ‘home’,
‘details/:id’ : ‘details’,
‘add’ : ‘add’
}
});
[/sourcecode]
The router.js file is pretty simple – create a Marionette AppRouter object and assign a collection of appRoutes. These appRoutes will match 1:1 with functions of the same name in our Controller object. Here we have the root url for the app ‘#’, which points to the ‘home’ function in the Controller. Then there’s ‘details/:id’ which is the URL for a contact’s details view and that points to the ‘details’ function in the Controller. Finally we have ‘add’ which points to the ‘add’ function in the Controller.
controller.js
[sourcecode language=”javascript”]
home: function() {
var view = window.App.views.contactsView;
this.renderView(view);
window.App.router.navigate(‘#’);
},
[/sourcecode]
The controller acts as the actual logic pertaining to our Router. As we mentioned earlier, the router and controller have a 1:1 relationship which means that every route defined in our router pertains to a function defined in our controller. Each function in the controller takes care of rendering the screen for each route. Using the ‘home’ function as an example, we create a new view, include a model if we are viewing a detail view, and then render it calling the controller’s renderView function. The renderView function will first destroy an existing view if it has already been rendered. This is to take care of event handling and make sure we don’t have zombie views and/or event handlers sticking around.
Models and Collections
[sourcecode language=”javascript”]
var Backbone = require(‘backbone’);
module.exports = ContactModel = Backbone.Model.extend({
idAttribute: ‘_id’
});
[/sourcecode]
[sourcecode language=”javascript”]
var Backbone = require(‘backbone’),
ContactModel = require(‘../models/contact’);
module.exports = ContactsCollection = Backbone.Collection.extend({
model: ContactModel,
url: ‘/api/contacts’
});
[/sourcecode]
This simple app has a single, basic model – the Contact. In addition there is a single collection of Contacts. Both are defined in their respective directories in the ‘src’ directory. You can see that the url for the collection has been set to that of our API. In addition, because we are using MongoDB we manually point the id attribute of our model to the _id field, which MongoDB uses by default as a unique identifier.
Views
There are 3 primary views for this application, a small contact “card” that appears as a collection on the homepage, the full details of a contact, and an add new contact form.
views/contacts.js
The contacts view file actually contains 2 views, a Marionette ItemView which is the individual “card” for a contact, and then a Marionette CollectionView which is just a collection of the “card” views for the collection of contacts. The contacts CollectionView has a listener on its collection for any change events, and if that happens the collection view is re-rendered. This is so that any new contacts added to the main collection will be rendered after they are added.
[sourcecode language=”javascript”]
var Marionette = require(‘backbone.marionette’);
var itemView = Marionette.ItemView.extend({
template: require(‘../../templates/contact_small.hbs’),
initialize: function() {
this.listenTo(this.model, ‘change’, this.render);
},
events: {
‘click’: ‘showDetails’
},
showDetails: function() {
window.App.core.vent.trigger(‘app:log’, ‘Contacts View: showDetails hit.’);
window.App.controller.details(this.model.id);
}
});
module.exports = CollectionView = Marionette.CollectionView.extend({
initialize: function() {
this.listenTo(this.collection, ‘change’, this.render);
},
itemView: itemView
});
[/sourcecode]
The ItemView itself simply has an event that will trigger the ‘details’ function in the controller. The ItemView also has a listener to its model so that if the details for a contact are changed, the view is re-rendered as well. Notice that the ItemView has a template defined as a Handlebars template and that its being referenced via a require(). Browserify will effectively replace that line with a precompiled javascript function of the template during build time. This will make view rendering much faster versus relying on the browser to do the compilation every time a view is rendered.
views/contact_details.js
The details view for a contact is pretty simple – just an event handler for going “<< Back” and a Handlebars template.
views/add.js
Finally we have the view that renders a form that is used to insert a new contact. The big piece of functionality here is the save function:
[sourcecode language=”javascript” firstline=”9″]
events: {
‘click a.save-button’: ‘save’
},
save: function(e) {
e.preventDefault();
var newContact = {
name: {
first: this.$el.find(‘#name_first’).val(),
last: this.$el.find(‘#name_last’).val()
},
email: this.$el.find(‘#email’).val(),
phone: this.$el.find(‘#phone’).val()
};
window.App.data.contacts.create(newContact);
window.App.core.vent.trigger(‘app:log’, ‘Add View: Saved new contact!’);
window.App.controller.home();
}
[/sourcecode]
Here we are defining a new contact that is just a generic JavaScript object that matches what our data objects look like on the MongoDB side. Then we simply pass that into Backbone’s collection.create() function – which will, using the default implementation of Backbone, make a POST call to the API url (defined in the collection earlier) passing the JSON object for the variable that was defined. Back on the server side, the node.js router has a listener for a POST to ‘api/contacts’ which calls the ‘add’ function in the (node.js) controller. The server’s contacts controller ‘add’ function will create a new contact model using Mongoose and save it to the MongoDB server:
[sourcecode language=”javascript” firstline=”17″ title=”server/controllers/contacts.js”]
add: function(req, res) {
var newContact = new models.Contact(req.body);
newContact.gravatar = md5(newContact.email);
newContact.save(function(err, contact) {
if (err)
res.json({});
console.log(‘successfully inserted contact: ‘ + contact._id);
res.json(contact);
});
},
[/sourcecode]
Its important to note that none of the front-end or back-end code does any kind of validation. This is obviously bad, but I omitted any simply to make the code more concise and easier to digest. This would be implementation that you will want to take care in your actual app. (And is always a good idea to validate on both the back-end and front-end to be safe!)
View Templates with Handlebars
As I mentioned earlier when I covered the back-end, the template engine we decided to use for both back-end and front-end is Handlebars. I did this to both keep things consistent and because I’m a fan of Handlebars. You can find the view templates in the ‘client/templates’ directory. Each is basically an HTML document with a .hbs file extension.
Part 4: Deploy with Heroku!
From here, we have a completely functional app, albeit a bit basic. The best way to test it, and the most logical next step, is to deploy it so its actually on the Internet! A fantastic service that is both free and super easy to use is Heroku! Heroku is basically a cloud based scalable hosting solution with a ton of amazing features and support for add-ons. Its an obvious choice for hosting an app like this. Lets take a look at the steps involved in launching the app.
Register a free account at Heroku.com
If you don’t have an account with Heroku, go there now and register a free account. It takes 2 seconds…
Download the Heroku Toolbelt
Once you have an account, make sure to download and install the Heroku Toolbelt, a command line tool that makes creating apps and pushing code to Heroku a breeze!
Create a Heroku app, Install Add-ons, and push your code live
Now that you have a Heroku account and the Toolbelt installed, you’re ready to create an app under your account, prepare the app, and push your code live. Along the way you’ll install a few Add-ons as well.
Note: All of the following commands should be executed from the root of the project directory.
[sourcecode light=”true”]$ heroku login[/sourcecode]
After logging in, you may be prompted to add SSH keys. Follow the directions as you go.
Next create a file named ‘Procfile’ and put the following single line of code in it:
[sourcecode]web: node server.js[/sourcecode]
Create an app under your Heroku account:
[sourcecode light=”true”]$ heroku create[/sourcecode]
Next we need to install the first add-on for the app, MongoHQ. MongoHQ is another cloud based hosting service specifically for MongoDB that works extremely well with Heroku. Like Heroku, MongoHQ has a sandbox level service that is absolutely free!
[sourcecode light=”true”]$ heroku addons:add mongohq[/sourcecode]
Note: In order to actually use add-ons, you will need to be sure your account has complete billing information. This is only because most services are scalable and have limited terms. Should you exceed those terms then billing would kick in.
Next go to the Heroku website and access your dashboard/Apps. Click on the app that was just created, and then click on the MongoHQ Add-on. Go into the Collections and add a collection for your app (i.e. “contacts”). Then go into Admin and select the Users tab. Create a new user to access the database (give it any username/password you want). Switch back to the Overview tab and copy the Mongo URI.
Edit server.js and the line that connects to the mongodb server will need to be changed to the Mongo URI that was just copied. Change the
Now we can push our code up to Heroku:
[sourcecode light=”true”]
$ git add .
$ git commit -m "Updates for Heroku"
[/sourcecode]
[sourcecode light=”true”]$ git push heroku master[/sourcecode]
Now that the app is setup, add-ons installed, and code pushed up we need Heroku to actually launch our server. To do this Heroku uses something called a dyno which will read the Procfile and execute its command:
[sourcecode light=”true”]$ heroku ps:scale web=1[/sourcecode]
Finally, launch your new Heorku app in a browser:
[sourcecode light=”true”]$ heroku open[/sourcecode]
Conclusion
For what appears to be a seemingly small app, there was a lot to cover. This boilerplate project should act as a great foundation for any future projects you want to quickly get up and running. Without having to worry about all the nuts and bolts that go into a standard web project, your time is freed up to worry about actually making the app!
If you have any questions at all about this project, please feel free to post a comment or hit me up on Twitter. Also, any suggestions or recommendations would be greatly appreciated! Thanks for reading, hope you find the code useful – now go build something!
Like this post? Want to show your support?
Wow! You made it this far! Long post huh? Well it took a while to create! I hope you liked it though. If so, I would really appreciate you showing your support:
Buy the book on Amazon.com! For only $1.99 you can buy the eBook (mobi) version for your Kindle!
Leave a review on Amazon.com! If you don’t want to spend any money (because well you just read the whole thing for free) why not leave a review anyway?! It would help me out greatly and anyone else interested in reading it that might find it on Amazon.
Thanks!!