diff --git a/.gitattributes b/.gitattributes
index 176a458f9..fcadb2cf9 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1 @@
-* text=auto
+* text eol=lf
diff --git a/.gitignore b/.gitignore
index 9e436b36f..78a6835b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,5 @@ demo
 .idea
 .DS_Store
 release.txt
-fixtures/bower.json
-fixtures/package.json
\ No newline at end of file
+test/fixtures/bower.json
+test/fixtures/package.json
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 5100940c2..814f378a3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,26 @@
+sudo: false
 language: node_js
 node_js:
   - '0.12'
+env:
+  global:
+    - SAUCE_USERNAME=fullstack_ci
+    - SAUCE_ACCESS_KEY=1a527ca6-4aa5-4618-86ce-0278bf158cbf
 before_install:
+  - ./scripts/sauce_connect_setup.sh
   - gem update --system
   - gem install sass --version "=3.3.7"
   - npm install -g bower grunt-cli
 services: mongodb
+cache:
+  directories:
+    - node_modules
+    - test/fixtures/node_modules
+    - test/fixtures/bower_components
 notifications:
   webhooks:
     urls:
-      - https://webhooks.gitter.im/e/911ed472ef19bcb27858
+      - secure: "DhPNqHXuUIeIGE9Ek3+63qhco+4MozXqMZL6dAKoq1MHQ2RAPO6SYIkUYZqDnuWYlwWao2EnTYcDREivIV/m/RnkP9bKlpX/n/RNJe+X4bwFaCU55fVKgkAFn3takSBC5SVoeTWHdWu3WhhqSdioWjT7mlE1wtt/RanSMb5Id8M="
     on_success: change  # options: [always|never|change] default: always
     on_failure: always  # options: [always|never|change] default: always
     on_start: false     # default: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ced95fa81..9d265082f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -582,4 +582,4 @@ Closes #18, #17
 
 #### Features
 
-* **gen:** include MongoDB as an option When selected, sets up database with Mongoose. Repl ([280cc84d](http://github.com/DaftMonk/generator-angular-fullstack/commit/280cc84d735c60b1c261540dceda34dd7f91c93c), closes [#2](http://github.com/DaftMonk/generator-angular-fullstack/issues/2))
\ No newline at end of file
+* **gen:** include MongoDB as an option When selected, sets up database with Mongoose. Repl ([280cc84d](http://github.com/DaftMonk/generator-angular-fullstack/commit/280cc84d735c60b1c261540dceda34dd7f91c93c), closes [#2](http://github.com/DaftMonk/generator-angular-fullstack/issues/2))
diff --git a/Gruntfile.js b/Gruntfile.js
index 9819a4041..38fc08efc 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,16 +1,19 @@
 'use strict';
-var markdown = require('marked');
+
 var semver = require('semver');
-var _s = require('underscore.string');
 var shell = require('shelljs');
-var process = require('child_process');
+var child_process = require('child_process');
 var Q = require('q');
 var helpers = require('yeoman-generator').test;
-var fs = require('fs-extra');
+var fs = require('fs');
 var path = require('path');
 
 module.exports = function (grunt) {
-  require('load-grunt-tasks')(grunt);
+  // Load grunt tasks automatically, when needed
+  require('jit-grunt')(grunt, {
+    buildcontrol: 'grunt-build-control',
+    changelog: 'grunt-conventional-changelog'
+  });
 
   grunt.initConfig({
     config: {
@@ -58,6 +61,11 @@ module.exports = function (grunt) {
       },
       all: ['Gruntfile.js', '*/index.js']
     },
+    env: {
+      fast: {
+        SKIP_E2E: true
+      }
+    },
     mochaTest: {
       test: {
         src: [
@@ -82,6 +90,16 @@ module.exports = function (grunt) {
           ]
         }]
       }
+    },
+    david: {
+      gen: {
+        options: {}
+      },
+      app: {
+        options: {
+          package: 'test/fixtures/package.json'
+        }
+      }
     }
   });
 
@@ -139,6 +157,7 @@ module.exports = function (grunt) {
         bootstrap: true,
         uibootstrap: true,
         mongoose: true,
+        testing: 'jasmine',
         auth: true,
         oauth: ['googleAuth', 'twitterAuth'],
         socketio: true
@@ -202,7 +221,6 @@ module.exports = function (grunt) {
   });
 
   grunt.registerTask('updateFixtures', 'updates package and bower fixtures', function() {
-    var done = this.async();
     var packageJson = fs.readFileSync(path.resolve('app/templates/_package.json'), 'utf8');
     var bowerJson = fs.readFileSync(path.resolve('app/templates/_bower.json'), 'utf8');
 
@@ -215,11 +233,8 @@ module.exports = function (grunt) {
     bowerJson = bowerJson.replace(/<%(.*)%>/g, '');
 
     // save files
-    fs.writeFile(path.resolve(__dirname + '/test/fixtures/package.json'), packageJson, function() {
-      fs.writeFile(path.resolve(__dirname + '/test/fixtures/bower.json'), bowerJson, function() {
-        done();
-      });
-    });
+    fs.writeFileSync(path.resolve(__dirname + '/test/fixtures/package.json'), packageJson);
+    fs.writeFileSync(path.resolve(__dirname + '/test/fixtures/bower.json'), bowerJson);
   });
 
   grunt.registerTask('installFixtures', 'install package and bower fixtures', function() {
@@ -227,12 +242,21 @@ module.exports = function (grunt) {
 
     shell.cd('test/fixtures');
     grunt.log.ok('installing npm dependencies for generated app');
-    process.exec('npm install --quiet', {cwd: '../fixtures'}, function (error, stdout, stderr) {
+    child_process.exec('npm install --quiet', {cwd: '../fixtures'}, function (error, stdout, stderr) {
 
       grunt.log.ok('installing bower dependencies for generated app');
-      process.exec('bower install', {cwd: '../fixtures'}, function (error, stdout, stderr) {
-        shell.cd('../../');
-        done();
+      child_process.exec('bower install', {cwd: '../fixtures'}, function (error, stdout, stderr) {
+
+        if(!process.env.SAUCE_USERNAME) {
+          grunt.log.ok('running npm run update-webdriver');
+          child_process.exec('npm run update-webdriver', function() {
+            shell.cd('../../');
+            done();
+          });
+        } else {
+          shell.cd('../../');
+          done();
+        }
       })
     });
   });
@@ -242,6 +266,24 @@ module.exports = function (grunt) {
     'installFixtures',
     'mochaTest'
   ]);
+  grunt.registerTask('test', function(target, option) {
+    if (target === 'fast') {
+      grunt.task.run([
+        'env:fast'
+      ]);
+    }
+
+    return grunt.task.run([
+      'updateFixtures',
+      'installFixtures',
+      'mochaTest'
+    ])
+  });
+
+  grunt.registerTask('deps', function(target) {
+    if (!target || target === 'app') grunt.task.run(['updateFixtures']);
+    grunt.task.run(['david:' + (target || '')]);
+  });
 
   grunt.registerTask('demo', [
     'clean:demo',
diff --git a/app/index.js b/app/index.js
index 194b337a6..ec954aa46 100644
--- a/app/index.js
+++ b/app/index.js
@@ -5,274 +5,415 @@ var util = require('util');
 var genUtils = require('../util.js');
 var yeoman = require('yeoman-generator');
 var chalk = require('chalk');
-var wiredep = require('wiredep');
 
 var AngularFullstackGenerator = yeoman.generators.Base.extend({
 
-  init: function () {
-    this.argument('name', { type: String, required: false });
-    this.appname = this.name || path.basename(process.cwd());
-    this.appname = this._.camelize(this._.slugify(this._.humanize(this.appname)));
-
-    this.option('app-suffix', {
-      desc: 'Allow a custom suffix to be added to the module name',
-      type: String,
-      required: 'false'
-    });
-    this.scriptAppName = this.appname + genUtils.appName(this);
-    this.appPath = this.env.options.appPath;
-    this.pkg = require('../package.json');
-
-    this.filters = {};
-  },
+  initializing: {
 
-  info: function () {
-    this.log(this.yeoman);
-    this.log('Out of the box I create an AngularJS app with an Express server.\n');
-  },
+    init: function () {
+      this.argument('name', { type: String, required: false });
+      this.appname = this.name || path.basename(process.cwd());
+      this.appname = this._.camelize(this._.slugify(this._.humanize(this.appname)));
 
-  checkForConfig: function() {
-    var cb = this.async();
+      this.option('app-suffix', {
+        desc: 'Allow a custom suffix to be added to the module name',
+        type: String,
+        required: 'false'
+      });
+      this.scriptAppName = this.appname + genUtils.appName(this);
+      this.appPath = this.env.options.appPath;
+      this.pkg = require('../package.json');
 
-    if(this.config.get('filters')) {
-      this.prompt([{
-        type: "confirm",
-        name: "skipConfig",
-        message: "Existing .yo-rc configuration found, would you like to use it?",
-        default: true,
-      }], function (answers) {
-        this.skipConfig = answers.skipConfig;
+      this.filters = {};
+
+      // dynamic assertion statement
+      this.does = this.is = function(foo) {
+        foo = this.engine(foo.replace(/\(;>%%<;\)/g, '<%')
+          .replace(/\(;>%<;\)/g, '%>'), this);
+        if (this.filters.should) {
+          return foo + '.should';
+        } else {
+          return 'expect(' + foo + ').to';
+        }
+      }.bind(this);
+    },
 
-        // NOTE: temp(?) fix for #403
-        if(typeof this.oauth==='undefined') {
-          var strategies = Object.keys(this.filters).filter(function(key) {
-            return key.match(/Auth$/) && key;
+    info: function () {
+      this.log(this.welcome);
+      this.log('Out of the box I create an AngularJS app with an Express server.\n');
+    },
+
+    checkForConfig: function() {
+      var cb = this.async();
+
+      if(this.config.get('filters')) {
+        this.prompt([{
+          type: 'confirm',
+          name: 'skipConfig',
+          message: 'Existing .yo-rc configuration found, would you like to use it?',
+          default: true,
+        }], function (answers) {
+          this.skipConfig = answers.skipConfig;
+
+          this.filters = this._.defaults(this.config.get('filters'), {
+            bootstrap: true,
+            uibootstrap: true,
+            jasmine: true
           });
 
-          if(strategies.length) this.config.set('oauth', true);
-        }
+          this.config.set('filters', this.filters);
+          this.config.forceSave();
 
+          cb();
+        }.bind(this));
+      } else {
         cb();
-      }.bind(this));
-    } else {
-      cb();
+      }
     }
+
   },
 
-  clientPrompts: function() {
-    if(this.skipConfig) return;
-    var cb = this.async();
+  prompting: {
 
-    this.log('# Client\n');
+    clientPrompts: function() {
+      if(this.skipConfig) return;
+      var cb = this.async();
 
-    this.prompt([{
-        type: "list",
-        name: "script",
-        message: "What would you like to write scripts with?",
-        choices: [ "JavaScript", "CoffeeScript"],
-        filter: function( val ) {
-          var filterMap = {
-            'JavaScript': 'js',
-            'CoffeeScript': 'coffee'
-          };
+      this.log('# Client\n');
 
-          return filterMap[val];
+      this.prompt([{
+          type: 'list',
+          name: 'script',
+          message: 'What would you like to write scripts with?',
+          choices: [ 'JavaScript', 'JavaScript + Babel', 'CoffeeScript'],
+          filter: function( val ) {
+            return {
+              'JavaScript': 'js',
+              'JavaScript + Babel': 'babel',
+              'CoffeeScript': 'coffee'
+            }[val];
+          }
+        }, {
+          type: 'list',
+          name: 'markup',
+          message: 'What would you like to write markup with?',
+          choices: ['HTML', 'Jade'],
+          filter: function( val ) { return val.toLowerCase(); }
+        }, {
+          type: 'list',
+          name: 'stylesheet',
+          default: 1,
+          message: 'What would you like to write stylesheets with?',
+          choices: [ 'CSS', 'Sass', 'Stylus', 'Less'],
+          filter: function( val ) { return val.toLowerCase(); }
+        },  {
+          type: 'list',
+          name: 'router',
+          default: 1,
+          message: 'What Angular router would you like to use?',
+          choices: [ 'ngRoute', 'uiRouter'],
+          filter: function( val ) { return val.toLowerCase(); }
+        }, {
+          type: 'confirm',
+          name: 'bootstrap',
+          message: 'Would you like to include Bootstrap?'
+        }, {
+          type: 'confirm',
+          name: 'uibootstrap',
+          message: 'Would you like to include UI Bootstrap?',
+          when: function (answers) {
+            return answers.bootstrap;
+          }
+        }], function (answers) {
+
+          // also set 'js' to true if using babel
+          if(answers.script === 'babel') { this.filters.js = true; }
+          this.filters[answers.script] = true;
+          this.filters[answers.markup] = true;
+          this.filters[answers.stylesheet] = true;
+          this.filters[answers.router] = true;
+          this.filters.bootstrap = !!answers.bootstrap;
+          this.filters.uibootstrap =  !!answers.uibootstrap;
+          cb();
+        }.bind(this));
+    },
+
+    serverPrompts: function() {
+      if(this.skipConfig) return;
+      var cb = this.async();
+      var self = this;
+
+      this.log('\n# Server\n');
+
+      this.prompt([{
+        type: 'checkbox',
+        name: 'odms',
+        message: 'What would you like to use for data modeling?',
+        choices: [
+          {
+            value: 'mongoose',
+            name: 'Mongoose (MongoDB)',
+            checked: true
+          },
+          {
+            value: 'sequelize',
+            name: 'Sequelize (MySQL, SQLite, MariaDB, PostgreSQL)',
+            checked: false
+          }
+        ]
+      }, {
+        type: 'list',
+        name: 'models',
+        message: 'What would you like to use for the default models?',
+        choices: [ 'Mongoose', 'Sequelize' ],
+        filter: function( val ) {
+          return val.toLowerCase();
+        },
+        when: function(answers) {
+          return answers.odms && answers.odms.length > 1;
         }
       }, {
-        type: "confirm",
-        name: "babel",
-        message: "Would you like to use Javascript ES6 in your client by preprocessing it with Babel?",
+        type: 'confirm',
+        name: 'auth',
+        message: 'Would you scaffold out an authentication boilerplate?',
         when: function (answers) {
-          return answers.script === 'js';
+          return answers.odms && answers.odms.length !== 0;
         }
       }, {
-        type: "list",
-        name: "markup",
-        message: "What would you like to write markup with?",
-        choices: [ "HTML", "Jade"],
-        filter: function( val ) { return val.toLowerCase(); }
-      }, {
-        type: "list",
-        name: "stylesheet",
-        default: 1,
-        message: "What would you like to write stylesheets with?",
-        choices: [ "CSS", "Sass", "Stylus", "Less"],
-        filter: function( val ) { return val.toLowerCase(); }
-      },  {
-        type: "list",
-        name: "router",
-        default: 1,
-        message: "What Angular router would you like to use?",
-        choices: [ "ngRoute", "uiRouter"],
-        filter: function( val ) { return val.toLowerCase(); }
-      }, {
-        type: "confirm",
-        name: "bootstrap",
-        message: "Would you like to include Bootstrap?"
+        type: 'checkbox',
+        name: 'oauth',
+        message: 'Would you like to include additional oAuth strategies?',
+        when: function (answers) {
+          return answers.auth;
+        },
+        choices: [
+          {
+            value: 'googleAuth',
+            name: 'Google',
+            checked: false
+          },
+          {
+            value: 'facebookAuth',
+            name: 'Facebook',
+            checked: false
+          },
+          {
+            value: 'twitterAuth',
+            name: 'Twitter',
+            checked: false
+          }
+        ]
       }, {
-        type: "confirm",
-        name: "uibootstrap",
-        message: "Would you like to include UI Bootstrap?",
+        type: 'confirm',
+        name: 'socketio',
+        message: 'Would you like to use socket.io?',
+        // to-do: should not be dependent on ODMs
         when: function (answers) {
-          return answers.bootstrap;
-        }
+          return answers.odms && answers.odms.length !== 0;
+        },
+        default: true
       }], function (answers) {
-        
-        this.filters.babel = !!answers.babel;
-        if(this.filters.babel){ this.filters.js = true; }
-        this.filters[answers.script] = true;
-        this.filters[answers.markup] = true;
-        this.filters[answers.stylesheet] = true;
-        this.filters[answers.router] = true;
-        this.filters.bootstrap = !!answers.bootstrap;
-        this.filters.uibootstrap =  !!answers.uibootstrap;
-      cb();
+        if(answers.socketio) this.filters.socketio = true;
+        if(answers.auth) this.filters.auth = true;
+        if(answers.odms && answers.odms.length > 0) {
+          var models;
+          if(!answers.models) {
+            models = answers.odms[0];
+          } else {
+            models = answers.models;
+          }
+          this.filters.models = true;
+          this.filters[models + 'Models'] = true;
+          answers.odms.forEach(function(odm) {
+            this.filters[odm] = true;
+          }.bind(this));
+        } else {
+          this.filters.noModels = true;
+        }
+        if(answers.oauth) {
+          if(answers.oauth.length) this.filters.oauth = true;
+          answers.oauth.forEach(function(oauthStrategy) {
+            this.filters[oauthStrategy] = true;
+          }.bind(this));
+        }
+
+        cb();
       }.bind(this));
-  },
+    },
 
-  serverPrompts: function() {
-    if(this.skipConfig) return;
-    var cb = this.async();
-    var self = this;
-
-    this.log('\n# Server\n');
-
-    this.prompt([{
-      type: "confirm",
-      name: "mongoose",
-      message: "Would you like to use mongoDB with Mongoose for data modeling?"
-    }, {
-      type: "confirm",
-      name: "auth",
-      message: "Would you scaffold out an authentication boilerplate?",
-      when: function (answers) {
-        return answers.mongoose;
-      }
-    }, {
-      type: 'checkbox',
-      name: 'oauth',
-      message: 'Would you like to include additional oAuth strategies?',
-      when: function (answers) {
-        return answers.auth;
-      },
-      choices: [
-        {
-          value: 'googleAuth',
-          name: 'Google',
-          checked: false
-        },
-        {
-          value: 'facebookAuth',
-          name: 'Facebook',
-          checked: false
+    projectPrompts: function() {
+      if(this.skipConfig) return;
+      var cb = this.async();
+      var self = this;
+
+      this.log('\n# Project\n');
+
+      this.prompt([{
+        type: 'list',
+        name: 'testing',
+        message: 'What would you like to write tests with?',
+        choices: [ 'Jasmine', 'Mocha + Chai + Sinon'],
+        filter: function( val ) {
+          var filterMap = {
+            'Jasmine': 'jasmine',
+            'Mocha + Chai + Sinon': 'mocha'
+          };
+
+          return filterMap[val];
+        }
+      }, {
+        type: 'list',
+        name: 'chai',
+        message: 'What would you like to write Chai assertions with?',
+        choices: ['Expect', 'Should'],
+        filter: function( val ) {
+          return val.toLowerCase();
         },
-        {
-          value: 'twitterAuth',
-          name: 'Twitter',
-          checked: false
+        when: function( answers ) {
+          return  answers.testing === 'mocha';
         }
-      ]
-    }, {
-      type: "confirm",
-      name: "socketio",
-      message: "Would you like to use socket.io?",
-      // to-do: should not be dependent on mongoose
-      when: function (answers) {
-        return answers.mongoose;
-      },
-      default: true
-    }], function (answers) {
-      if(answers.socketio) this.filters.socketio = true;
-      if(answers.mongoose) this.filters.mongoose = true;
-      if(answers.auth) this.filters.auth = true;
-      if(answers.oauth) {
-        if(answers.oauth.length) this.filters.oauth = true;
-        answers.oauth.forEach(function(oauthStrategy) {
-          this.filters[oauthStrategy] = true;
-        }.bind(this));
-      }
+      }], function (answers) {
+        /**
+         * Default to grunt until gulp support is implemented
+         */
+        this.filters.grunt = true;
+
+        this.filters[answers.testing] = true;
+        if (answers.testing === 'mocha') {
+          this.filters.jasmine = false;
+          this.filters.should = false;
+          this.filters.expect = false;
+          this.filters[answers.chai] = true;
+        }
+        if (answers.testing === 'jasmine') {
+          this.filters.mocha = false;
+          this.filters.should = false;
+          this.filters.expect = false;
+        }
+
+        cb();
+      }.bind(this));
+    }
 
-      cb();
-    }.bind(this));
   },
 
-  saveSettings: function() {
-    if(this.skipConfig) return;
-    this.config.set('insertRoutes', true);
-    this.config.set('registerRoutesFile', 'server/routes.js');
-    this.config.set('routesNeedle', '// Insert routes below');
+  configuring: {
 
-    this.config.set('routesBase', '/api/');
-    this.config.set('pluralizeRoutes', true);
+    saveSettings: function() {
+      if(this.skipConfig) return;
+      this.config.set('endpointDirectory', 'server/api/');
+      this.config.set('insertRoutes', true);
+      this.config.set('registerRoutesFile', 'server/routes.js');
+      this.config.set('routesNeedle', '// Insert routes below');
 
-    this.config.set('insertSockets', true);
-    this.config.set('registerSocketsFile', 'server/config/socketio.js');
-    this.config.set('socketsNeedle', '// Insert sockets below');
+      this.config.set('routesBase', '/api/');
+      this.config.set('pluralizeRoutes', true);
+
+      this.config.set('insertSockets', true);
+      this.config.set('registerSocketsFile', 'server/config/socketio.js');
+      this.config.set('socketsNeedle', '// Insert sockets below');
+
+      this.config.set('insertModels', true);
+      this.config.set('registerModelsFile', 'server/sqldb/index.js');
+      this.config.set('modelsNeedle', '// Insert models below');
+
+      this.config.set('filters', this.filters);
+      this.config.forceSave();
+    },
+
+    ngComponent: function() {
+      if(this.skipConfig) return;
+      var appPath = 'client/app/';
+      var extensions = [];
+      var filters = [
+        'ngroute',
+        'uirouter',
+        'jasmine',
+        'mocha',
+        'expect',
+        'should'
+      ].filter(function(v) {return this.filters[v];}, this);
+
+      if(this.filters.ngroute) filters.push('ngroute');
+      if(this.filters.uirouter) filters.push('uirouter');
+      if(this.filters.babel) extensions.push('babel');
+      if(this.filters.coffee) extensions.push('coffee');
+      if(this.filters.js) extensions.push('js');
+      if(this.filters.html) extensions.push('html');
+      if(this.filters.jade) extensions.push('jade');
+      if(this.filters.css) extensions.push('css');
+      if(this.filters.stylus) extensions.push('styl');
+      if(this.filters.sass) extensions.push('scss');
+      if(this.filters.less) extensions.push('less');
+
+      this.composeWith('ng-component', {
+        options: {
+          'routeDirectory': appPath,
+          'directiveDirectory': appPath,
+          'filterDirectory': appPath,
+          'serviceDirectory': appPath,
+          'filters': filters,
+          'extensions': extensions,
+          'basePath': 'client'
+        }
+      }, { local: require.resolve('generator-ng-component/app/index.js') });
+    },
+
+    ngModules: function() {
+      var angModules = [
+        "'ngCookies'",
+        "'ngResource'",
+        "'ngSanitize'"
+      ];
+      if(this.filters.ngroute) angModules.push("'ngRoute'");
+      if(this.filters.socketio) angModules.push("'btford.socket-io'");
+      if(this.filters.uirouter) angModules.push("'ui.router'");
+      if(this.filters.uibootstrap) angModules.push("'ui.bootstrap'");
+
+      this.angularModules = '\n  ' + angModules.join(',\n  ') +'\n';
+    }
 
-    this.config.set('filters', this.filters);
-    this.config.forceSave();
   },
 
-  compose: function() {
-    if(this.skipConfig) return;
-    var appPath = 'client/app/';
-    var extensions = [];
-    var filters = [];
-
-    if(this.filters.ngroute) filters.push('ngroute');
-    if(this.filters.uirouter) filters.push('uirouter');
-    if(this.filters.babel) extensions.push('babel');
-    if(this.filters.coffee) extensions.push('coffee');
-    if(this.filters.js) extensions.push('js');
-    if(this.filters.html) extensions.push('html');
-    if(this.filters.jade) extensions.push('jade');
-    if(this.filters.css) extensions.push('css');
-    if(this.filters.stylus) extensions.push('styl');
-    if(this.filters.sass) extensions.push('scss');
-    if(this.filters.less) extensions.push('less');
-
-    this.composeWith('ng-component', {
-      options: {
-        'routeDirectory': appPath,
-        'directiveDirectory': appPath,
-        'filterDirectory': appPath,
-        'serviceDirectory': appPath,
-        'filters': filters,
-        'extensions': extensions,
-        'basePath': 'client'
+  default: {},
+
+  writing: {
+
+    generateProject: function() {
+      this.sourceRoot(path.join(__dirname, './templates'));
+      genUtils.processDirectory(this, '.', '.');
+    },
+
+    generateEndpoint: function() {
+      var models;
+      if (this.filters.mongooseModels) {
+        models = 'mongoose';
+      } else if (this.filters.sequelizeModels) {
+        models = 'sequelize';
       }
-    }, { local: require.resolve('generator-ng-component/app/index.js') });
-  },
+      this.composeWith('angular-fullstack:endpoint', {
+        options: {
+          route: '/api/things',
+          models: models
+        },
+        args: ['thing']
+      });
+    }
 
-  ngModules: function() {
-    this.filters = this._.defaults(this.config.get('filters'), {
-      bootstrap: true,
-      uibootstrap: true
-    });
-
-    var angModules = [
-      "'ngCookies'",
-      "'ngResource'",
-      "'ngSanitize'"
-    ];
-    if(this.filters.ngroute) angModules.push("'ngRoute'");
-    if(this.filters.socketio) angModules.push("'btford.socket-io'");
-    if(this.filters.uirouter) angModules.push("'ui.router'");
-    if(this.filters.uibootstrap) angModules.push("'ui.bootstrap'");
-
-    this.angularModules = "\n  " + angModules.join(",\n  ") +"\n";
   },
 
-  generate: function() {
-    this.sourceRoot(path.join(__dirname, './templates'));
-    genUtils.processDirectory(this, '.', '.');
+  install: {
+
+    installDeps: function() {
+      this.installDependencies({
+        skipInstall: this.options['skip-install']
+      });
+    }
+
   },
 
-  end: function() {
-    this.installDependencies({
-      skipInstall: this.options['skip-install']
-    });
-  }
+  end: {}
+
 });
 
 module.exports = AngularFullstackGenerator;
diff --git a/app/templates/.buildignore b/app/templates/.buildignore
index fc98b8eb5..3ae6d06a2 100644
--- a/app/templates/.buildignore
+++ b/app/templates/.buildignore
@@ -1 +1 @@
-*.coffee
\ No newline at end of file
+*.coffee
diff --git a/app/templates/.jscs.json b/app/templates/.jscs.json
new file mode 100644
index 000000000..99393d5f6
--- /dev/null
+++ b/app/templates/.jscs.json
@@ -0,0 +1,44 @@
+{
+  "maximumLineLength": {
+    "value": 100,
+    "allowComments": true,
+    "allowRegex": true
+  },
+  "disallowMixedSpacesAndTabs": true,
+  "disallowMultipleLineStrings": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+  "disallowSpaceBeforeBinaryOperators": [","],
+  "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+  "disallowSpacesInAnonymousFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInFunctionDeclaration": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInNamedFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideArrayBrackets": true,
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingComma": true,
+  "disallowTrailingWhitespace": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpacesInConditionalExpression": {
+    "afterTest": true,
+    "beforeConsequent": true,
+    "afterConsequent": true,
+    "beforeAlternate": true
+  },
+  "requireSpacesInFunction": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "validateLineBreaks": "LF",
+  "validateParameterSeparator": ", "
+}
diff --git a/app/templates/.travis.yml b/app/templates/.travis.yml
index 5112a8e88..c12f57edb 100644
--- a/app/templates/.travis.yml
+++ b/app/templates/.travis.yml
@@ -1,9 +1,8 @@
 language: node_js
 node_js:
-  - '0.10'
-  - '0.11'
+  - '0.12'
 before_script:
   - npm install -g bower grunt-cli<% if (filters.sass) { %>
   - gem install sass<% } %>
   - bower install
-services: mongodb
\ No newline at end of file
+services: mongodb
diff --git a/app/templates/Gruntfile.js b/app/templates/Gruntfile.js
index 6685f5108..666681a89 100644
--- a/app/templates/Gruntfile.js
+++ b/app/templates/Gruntfile.js
@@ -16,7 +16,8 @@ module.exports = function (grunt) {
     ngtemplates: 'grunt-angular-templates',
     cdnify: 'grunt-google-cdn',
     protractor: 'grunt-protractor-runner',
-    buildcontrol: 'grunt-build-control'
+    buildcontrol: 'grunt-build-control',
+    istanbul_check_coverage: 'grunt-mocha-istanbul'
   });
 
   // Time how long tasks take. Can help when optimizing build times
@@ -38,13 +39,13 @@ module.exports = function (grunt) {
       },
       dev: {
         options: {
-          script: 'server/app.js',
+          script: 'server',
           debug: true
         }
       },
       prod: {
         options: {
-          script: 'dist/server/app.js'
+          script: 'dist/server'
         }
       }
     },
@@ -53,102 +54,73 @@ module.exports = function (grunt) {
         url: 'http://localhost:<%%= express.options.port %>'
       }
     },
-    watch: {
+    watch: {<% if(filters.babel) { %>
+      babel: {
+        files: ['<%%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js'],
+        tasks: ['newer:babel:client']
+      },<% } %>
       injectJS: {
         files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.js',
-          '!<%%= yeoman.client %>/{app,components}/**/*.spec.js',
-          '!<%%= yeoman.client %>/{app,components}/**/*.mock.js',
-          '!<%%= yeoman.client %>/app/app.js'],
+          '<%%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js',
+          '!<%%= yeoman.client %>/app/app.js'
+        ],
         tasks: ['injector:scripts']
       },
       injectCss: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.css'
-        ],
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.css'],
         tasks: ['injector:css']
       },
       mochaTest: {
-        files: ['server/**/*.spec.js'],
+        files: ['server/**/*.{spec,integration}.js'],
         tasks: ['env:test', 'mochaTest']
       },
       jsTest: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.spec.js',
-          '<%%= yeoman.client %>/{app,components}/**/*.mock.js'
-        ],
-        tasks: ['newer:jshint:all', 'karma']
-      },<% if(filters.stylus) { %>
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js'],
+        tasks: ['newer:jshint:all', 'wiredep:test', 'karma']
+      },<% if (filters.stylus) { %>
       injectStylus: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.styl'],
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.styl'],
         tasks: ['injector:stylus']
       },
       stylus: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.styl'],
-        tasks: ['stylus', 'autoprefixer']
-      },<% } %><% if(filters.sass) { %>
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.styl'],
+        tasks: ['stylus', 'postcss']
+      },<% } if (filters.sass) { %>
       injectSass: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
         tasks: ['injector:sass']
       },
       sass: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
-        tasks: ['sass', 'autoprefixer']
-      },<% } %><% if(filters.less) { %>
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
+        tasks: ['sass', 'postcss']
+      },<% } if (filters.less) { %>
       injectLess: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.less'],
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.less'],
         tasks: ['injector:less']
       },
       less: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.less'],
-        tasks: ['less', 'autoprefixer']
-      },<% } %><% if(filters.jade) { %>
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.less'],
+        tasks: ['less', 'postcss']
+      },<% } if (filters.jade) { %>
       jade: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/*',
-          '<%%= yeoman.client %>/{app,components}/**/*.jade'],
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.jade'],
         tasks: ['jade']
-      },<% } %><% if(filters.coffee) { %>
+      },<% } if (filters.coffee) { %>
       coffee: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.{coffee,litcoffee,coffee.md}',
-          '!<%%= yeoman.client %>/{app,components}/**/*.spec.{coffee,litcoffee,coffee.md}'
-        ],
+        files: ['<%%= yeoman.client %>/{app,components}/**/!(*.spec).{coffee,litcoffee,coffee.md}'],
         tasks: ['newer:coffee', 'injector:scripts']
       },
       coffeeTest: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.spec.{coffee,litcoffee,coffee.md}'
-        ],
+        files: ['<%%= yeoman.client %>/{app,components}/**/*.spec.{coffee,litcoffee,coffee.md}'],
         tasks: ['karma']
-      },<% } %><% if(filters.babel) { %>
-      babel: {
-        files: [
-          '<%%= yeoman.client %>/{app,components}/**/*.js',
-          '!<%%= yeoman.client %>/{app,components}/**/*.spec.js'
-        ],
-        tasks: ['babel']
       },<% } %>
       gruntfile: {
         files: ['Gruntfile.js']
       },
       livereload: {
         files: [
-          '{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.css',
-          '{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.html',
-          <% if(filters.babel) { %>
-          '.tmp/{app,components}/**/*.js',
-          <% } else { %>
-          '{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.js',
-          <% } %>
-          '!{.tmp,<%%= yeoman.client %>}{app,components}/**/*.spec.js',
-          '!{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.mock.js',
+          '{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.{css,html}',
+          '{.tmp,<%%= yeoman.client %>}/{app,components}/**/!(*.spec|*.mock).js',
           '<%%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}'
         ],
         options: {
@@ -156,15 +128,17 @@ module.exports = function (grunt) {
         }
       },
       express: {
-        files: [
-          'server/**/*.{js,json}'
-        ],
+        files: ['server/**/*.{js,json}'],
         tasks: ['express:dev', 'wait'],
         options: {
           livereload: true,
-          nospawn: true //Without this option specified express won't be reloaded
+          spawn: false //Without this option specified express won't be reloaded
         }
-      }
+      },
+      bower: {
+        files: ['bower.json'],
+        tasks: ['wiredep']
+      },
     },
 
     // Make sure code styles are up to par and there are no obvious mistakes
@@ -177,27 +151,31 @@ module.exports = function (grunt) {
         options: {
           jshintrc: 'server/.jshintrc'
         },
-        src: [
-          'server/**/*.js',
-          '!server/**/*.spec.js'
-        ]
+        src: ['server/**/!(*.spec|*.integration).js']
       },
       serverTest: {
         options: {
           jshintrc: 'server/.jshintrc-spec'
         },
-        src: ['server/**/*.spec.js']
+        src: ['server/**/*.{spec,integration}.js']
       },
-      all: [
-        '<%%= yeoman.client %>/{app,components}/**/*.js',
-        '!<%%= yeoman.client %>/{app,components}/**/*.spec.js',
-        '!<%%= yeoman.client %>/{app,components}/**/*.mock.js'
-      ],
+      all: ['<%%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js'],
       test: {
-        src: [
-          '<%%= yeoman.client %>/{app,components}/**/*.spec.js',
-          '<%%= yeoman.client %>/{app,components}/**/*.mock.js'
-        ]
+        src: ['<%%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js']
+      }
+    },
+
+    jscs: {
+      options: {
+        config: ".jscs.json"
+      },
+      main: {
+        files: {
+          src: [
+            '<%%= yeoman.client %>/app/**/*.js',
+            'server/**/*.js'
+          ]
+        }
       }
     },
 
@@ -208,10 +186,7 @@ module.exports = function (grunt) {
           dot: true,
           src: [
             '.tmp',
-            '<%%= yeoman.dist %>/*',
-            '!<%%= yeoman.dist %>/.git*',
-            '!<%%= yeoman.dist %>/.openshift',
-            '!<%%= yeoman.dist %>/Procfile'
+            '<%%= yeoman.dist %>/!(.git*|.openshift|Procfile)**'
           ]
         }]
       },
@@ -219,9 +194,12 @@ module.exports = function (grunt) {
     },
 
     // Add vendor prefixed styles
-    autoprefixer: {
+    postcss: {
       options: {
-        browsers: ['last 1 version']
+        map: true,
+        processors: [
+          require('autoprefixer-core')({browsers: ['last 1 version']})
+        ]
       },
       dist: {
         files: [{
@@ -245,7 +223,7 @@ module.exports = function (grunt) {
     // Use nodemon to run server in debug mode with an initial breakpoint
     nodemon: {
       debug: {
-        script: 'server/app.js',
+        script: 'server',
         options: {
           nodeArgs: ['--debug-brk'],
           env: {
@@ -267,26 +245,36 @@ module.exports = function (grunt) {
       }
     },
 
-    // Automatically inject Bower components into the app
+    // Automatically inject Bower components into the app and karma.conf.js
     wiredep: {
-      target: {
+      options: {
+        exclude: [
+          /bootstrap.js/,
+          '/json3/',
+          '/es5-shim/'<% if(!filters.css) { %>,
+          /font-awesome\.css/<% if(filters.bootstrap) { %>,
+          /bootstrap\.css/<% if(filters.sass) { %>,
+          /bootstrap-sass-official/<% }}} %>
+        ]
+      },
+      client: {
         src: '<%%= yeoman.client %>/index.html',
         ignorePath: '<%%= yeoman.client %>/',
-        exclude: [/bootstrap-sass-official/, /bootstrap.js/, '/json3/', '/es5-shim/'<% if(!filters.css) { %>, /bootstrap.css/, /font-awesome.css/ <% } %>]
+      },
+      test: {
+        src: './karma.conf.js',
+        devDependencies: true
       }
     },
 
     // Renames files for browser caching purposes
-    rev: {
+    filerev: {
       dist: {
-        files: {
-          src: [
-            '<%%= yeoman.dist %>/public/{,*/}*.js',
-            '<%%= yeoman.dist %>/public/{,*/}*.css',
-            '<%%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
-            '<%%= yeoman.dist %>/public/assets/fonts/*'
-          ]
-        }
+        src: [
+          '<%%= yeoman.dist %>/client/!(bower_components){,*/}*.{js,css}',
+          '<%%= yeoman.dist %>/client/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
+          '<%%= yeoman.dist %>/client/assets/fonts/*'
+        ]
       }
     },
 
@@ -296,19 +284,19 @@ module.exports = function (grunt) {
     useminPrepare: {
       html: ['<%%= yeoman.client %>/index.html'],
       options: {
-        dest: '<%%= yeoman.dist %>/public'
+        dest: '<%%= yeoman.dist %>/client'
       }
     },
 
     // Performs rewrites based on rev and the useminPrepare configuration
     usemin: {
-      html: ['<%%= yeoman.dist %>/public/{,*/}*.html'],
-      css: ['<%%= yeoman.dist %>/public/{,*/}*.css'],
-      js: ['<%%= yeoman.dist %>/public/{,*/}*.js'],
+      html: ['<%%= yeoman.dist %>/client/!(bower_components){,*/}*.html'],
+      css: ['<%%= yeoman.dist %>/client/!(bower_components){,*/}*.css'],
+      js: ['<%%= yeoman.dist %>/client/!(bower_components){,*/}*.js'],
       options: {
         assetsDirs: [
-          '<%%= yeoman.dist %>/public',
-          '<%%= yeoman.dist %>/public/assets/images'
+          '<%%= yeoman.dist %>/client',
+          '<%%= yeoman.dist %>/client/assets/images'
         ],
         // This is so we update image references in our ng-templates
         patterns: {
@@ -325,19 +313,8 @@ module.exports = function (grunt) {
         files: [{
           expand: true,
           cwd: '<%%= yeoman.client %>/assets/images',
-          src: '{,*/}*.{png,jpg,jpeg,gif}',
-          dest: '<%%= yeoman.dist %>/public/assets/images'
-        }]
-      }
-    },
-
-    svgmin: {
-      dist: {
-        files: [{
-          expand: true,
-          cwd: '<%%= yeoman.client %>/assets/images',
-          src: '{,*/}*.svg',
-          dest: '<%%= yeoman.dist %>/public/assets/images'
+          src: '{,*/}*.{png,jpg,jpeg,gif,svg}',
+          dest: '<%%= yeoman.dist %>/client/assets/images'
         }]
       }
     },
@@ -386,7 +363,7 @@ module.exports = function (grunt) {
     // Replace Google CDN references
     cdnify: {
       dist: {
-        html: ['<%%= yeoman.dist %>/public/*.html']
+        html: ['<%%= yeoman.dist %>/client/*.html']
       }
     },
 
@@ -397,7 +374,7 @@ module.exports = function (grunt) {
           expand: true,
           dot: true,
           cwd: '<%%= yeoman.client %>',
-          dest: '<%%= yeoman.dist %>/public',
+          dest: '<%%= yeoman.dist %>/client',
           src: [
             '*.{ico,png,txt}',
             '.htaccess',
@@ -409,7 +386,7 @@ module.exports = function (grunt) {
         }, {
           expand: true,
           cwd: '.tmp/images',
-          dest: '<%%= yeoman.dist %>/public/assets/images',
+          dest: '<%%= yeoman.dist %>/client/assets/images',
           src: ['generated/*']
         }, {
           expand: true,
@@ -453,19 +430,19 @@ module.exports = function (grunt) {
     // Run some tasks in parallel to speed up the build process
     concurrent: {
       server: [<% if(filters.coffee) { %>
-        'coffee',<% } %><% if(filters.babel) { %>
-        'babel',<% } %><% if(filters.jade) { %>
-        'jade',<% } %><% if(filters.stylus) { %>
-        'stylus',<% } %><% if(filters.sass) { %>
-        'sass',<% } %><% if(filters.less) { %>
+        'coffee',<% } if(filters.babel) { %>
+        'newer:babel:client',<% } if(filters.jade) { %>
+        'jade',<% } if(filters.stylus) { %>
+        'stylus',<% } if(filters.sass) { %>
+        'sass',<% } if(filters.less) { %>
         'less',<% } %>
       ],
       test: [<% if(filters.coffee) { %>
-        'coffee',<% } %><% if(filters.babel) { %>
-        'babel',<% } %><% if(filters.jade) { %>
-        'jade',<% } %><% if(filters.stylus) { %>
-        'stylus',<% } %><% if(filters.sass) { %>
-        'sass',<% } %><% if(filters.less) { %>
+        'coffee',<% } if(filters.babel) { %>
+        'newer:babel:client',<% } if(filters.jade) { %>
+        'jade',<% } if(filters.stylus) { %>
+        'stylus',<% } if(filters.sass) { %>
+        'sass',<% } if(filters.less) { %>
         'less',<% } %>
       ],
       debug: {
@@ -478,14 +455,13 @@ module.exports = function (grunt) {
         }
       },
       dist: [<% if(filters.coffee) { %>
-        'coffee',<% } %><% if(filters.babel) { %>
-        'babel',<% } %><% if(filters.jade) { %>
-        'jade',<% } %><% if(filters.stylus) { %>
-        'stylus',<% } %><% if(filters.sass) { %>
-        'sass',<% } %><% if(filters.less) { %>
+        'coffee',<% } if(filters.babel) { %>
+        'newer:babel:client',<% } if(filters.jade) { %>
+        'jade',<% } if(filters.stylus) { %>
+        'stylus',<% } if(filters.sass) { %>
+        'sass',<% } if(filters.less) { %>
         'less',<% } %>
-        'imagemin',
-        'svgmin'
+        'imagemin'
       ]
     },
 
@@ -499,9 +475,53 @@ module.exports = function (grunt) {
 
     mochaTest: {
       options: {
-        reporter: 'spec'
+        reporter: 'spec',
+        require: 'mocha.conf.js',
+        timeout: 5000 // set default mocha spec timeout
+      },
+      unit: {
+        src: ['server/**/*.spec.js']
       },
-      src: ['server/**/*.spec.js']
+      integration: {
+        src: ['server/**/*.integration.js']
+      }
+    },
+
+    mocha_istanbul: {
+      unit: {
+        options: {
+          excludes: ['**/*.{spec,mock,integration}.js'],
+          reporter: 'spec',
+          require: ['mocha.conf.js'],
+          mask: '**/*.spec.js',
+          coverageFolder: 'coverage/server/unit'
+        },
+        src: 'server'
+      },
+      integration: {
+        options: {
+          excludes: ['**/*.{spec,mock,integration}.js'],
+          reporter: 'spec',
+          require: ['mocha.conf.js'],
+          mask: '**/*.integration.js',
+          coverageFolder: 'coverage/server/integration'
+        },
+        src: 'server'
+      }
+    },
+
+    istanbul_check_coverage: {
+      default: {
+        options: {
+          coverageFolder: 'coverage/**',
+          check: {
+            lines: 80,
+            statements: 80,
+            branches: 80,
+            functions: 80
+          }
+        }
+      }
     },
 
     protractor: {
@@ -525,7 +545,7 @@ module.exports = function (grunt) {
         NODE_ENV: 'production'
       },
       all: localConfig
-    },<% if(filters.jade) { %>
+    },<% if (filters.jade) { %>
 
     // Compiles Jade to html
     jade: {
@@ -538,14 +558,12 @@ module.exports = function (grunt) {
         files: [{
           expand: true,
           cwd: '<%%= yeoman.client %>',
-          src: [
-            '{app,components}/**/*.jade'
-          ],
+          src: ['{app,components}/**/*.jade'],
           dest: '.tmp',
           ext: '.html'
         }]
       }
-    },<% } %><% if(filters.coffee) { %>
+    },<% } if (filters.coffee) { %>
 
     // Compiles CoffeeScript to JavaScript
     coffee: {
@@ -557,82 +575,59 @@ module.exports = function (grunt) {
         files: [{
           expand: true,
           cwd: 'client',
-          src: [
-            '{app,components}/**/*.coffee',
-            '!{app,components}/**/*.spec.coffee'
-          ],
+          src: ['{app,components}/**/!(*.spec).coffee'],
           dest: '.tmp',
           ext: '.js'
         }]
       }
-    },<% } %><% if(filters.babel) { %>
+    },<% } if(filters.babel) { %>
 
     // Compiles ES6 to JavaScript using Babel
     babel: {
-      options: { 
+      options: {
         sourceMap: true
       },
-      server: {
+      client: {
         files: [{
           expand: true,
-          cwd: 'client',
-          src: [
-            '{app,components}/**/*.js',
-            '!{app,components}/**/*.spec.js'
-          ],
+          cwd: '<%%= yeoman.client %>',
+          src: ['{app,components}/**/!(*.spec).js'],
           dest: '.tmp'
         }]
       }
-    },<% } %><% if(filters.stylus) { %>
+    },<% } if(filters.stylus) { %>
 
     // Compiles Stylus to CSS
     stylus: {
       server: {
         options: {
-          paths: [
-            '<%%= yeoman.client %>/bower_components',
-            '<%%= yeoman.client %>/app',
-            '<%%= yeoman.client %>/components'
-          ],
           "include css": true
         },
         files: {
           '.tmp/app/app.css' : '<%%= yeoman.client %>/app/app.styl'
         }
       }
-    },<% } %><% if(filters.sass) { %>
+    },<% } if (filters.sass) { %>
 
     // Compiles Sass to CSS
     sass: {
       server: {
         options: {
-          loadPath: [
-            '<%%= yeoman.client %>/bower_components',
-            '<%%= yeoman.client %>/app',
-            '<%%= yeoman.client %>/components'
-          ],
           compass: false
         },
         files: {
           '.tmp/app/app.css' : '<%%= yeoman.client %>/app/app.scss'
         }
       }
-    },<% } %><% if(filters.less) { %>
+    },<% } if (filters.less) { %>
 
     // Compiles Less to CSS
     less: {
-      options: {
-        paths: [
-          '<%%= yeoman.client %>/bower_components',
-          '<%%= yeoman.client %>/app',
-          '<%%= yeoman.client %>/components'
-        ]
-      },
       server: {
         files: {
           '.tmp/app/app.css' : '<%%= yeoman.client %>/app/app.less'
         }
-      },
+      }
     },<% } %>
 
     injector: {
@@ -652,26 +647,21 @@ module.exports = function (grunt) {
         },
         files: {
           '<%%= yeoman.client %>/index.html': [
-               [
-                 <% if(filters.babel) { %>
-                 '.tmp/{app,components}/**/*.js',
-                 <% } else { %>
-                 '{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.js',
-                 <% } %>
-                 '!{.tmp,<%%= yeoman.client %>}/app/app.js',               
-                 '!{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.spec.js',
-                 '!{.tmp,<%%= yeoman.client %>}/{app,components}/**/*.mock.js'               
+               [<% if(filters.babel) { %>
+                 '.tmp/{app,components}/**/!(*.spec|*.mock).js',<% } else { %>
+                 '{.tmp,<%%= yeoman.client %>}/{app,components}/**/!(*.spec|*.mock).js',<% } %>
+                 '!{.tmp,<%%= yeoman.client %>}/app/app.js'
                ]
             ]
         }
-      },<% if(filters.stylus) { %>
+      },<% if (filters.stylus) { %>
 
       // Inject component styl into app.styl
       stylus: {
         options: {
           transform: function(filePath) {
             filePath = filePath.replace('/client/app/', '');
-            filePath = filePath.replace('/client/components/', '');
+            filePath = filePath.replace('/client/components/', '../components/');
             return '@import \'' + filePath + '\';';
           },
           starttag: '// injector',
@@ -683,14 +673,14 @@ module.exports = function (grunt) {
             '!<%%= yeoman.client %>/app/app.styl'
           ]
         }
-      },<% } %><% if(filters.sass) { %>
+      },<% } if (filters.sass) { %>
 
       // Inject component scss into app.scss
       sass: {
         options: {
           transform: function(filePath) {
             filePath = filePath.replace('/client/app/', '');
-            filePath = filePath.replace('/client/components/', '');
+            filePath = filePath.replace('/client/components/', '../components/');
             return '@import \'' + filePath + '\';';
           },
           starttag: '// injector',
@@ -702,14 +692,14 @@ module.exports = function (grunt) {
             '!<%%= yeoman.client %>/app/app.{scss,sass}'
           ]
         }
-      },<% } %><% if(filters.less) { %>
+      },<% } if (filters.less) { %>
 
       // Inject component less into app.less
       less: {
         options: {
           transform: function(filePath) {
             filePath = filePath.replace('/client/app/', '');
-            filePath = filePath.replace('/client/components/', '');
+            filePath = filePath.replace('/client/components/', '../components/');
             return '@import \'' + filePath + '\';';
           },
           starttag: '// injector',
@@ -767,28 +757,28 @@ module.exports = function (grunt) {
     if (target === 'debug') {
       return grunt.task.run([
         'clean:server',
-        'env:all',<% if(filters.stylus) { %>
-        'injector:stylus', <% } %><% if(filters.less) { %>
-        'injector:less', <% } %><% if(filters.sass) { %>
-        'injector:sass', <% } %>
+        'env:all',<% if (filters.stylus) { %>
+        'injector:stylus',<% } if (filters.less) { %>
+        'injector:less',<% } if (filters.sass) { %>
+        'injector:sass',<% } %>
         'concurrent:server',
         'injector',
-        'wiredep',
-        'autoprefixer',
+        'wiredep:client',
+        'postcss',
         'concurrent:debug'
       ]);
     }
 
     grunt.task.run([
       'clean:server',
-      'env:all',<% if(filters.stylus) { %>
-      'injector:stylus', <% } %><% if(filters.less) { %>
-      'injector:less', <% } %><% if(filters.sass) { %>
-      'injector:sass', <% } %>
+      'env:all',<% if (filters.stylus) { %>
+      'injector:stylus',<% } if (filters.less) { %>
+      'injector:less',<% } if (filters.sass) { %>
+      'injector:sass',<% } %>
       'concurrent:server',
       'injector',
-      'wiredep',
-      'autoprefixer',
+      'wiredep:client',
+      'postcss',
       'express:dev',
       'wait',
       'open',
@@ -801,44 +791,94 @@ module.exports = function (grunt) {
     grunt.task.run(['serve']);
   });
 
-  grunt.registerTask('test', function(target) {
+  grunt.registerTask('test', function(target, option) {
     if (target === 'server') {
       return grunt.task.run([
         'env:all',
         'env:test',
-        'mochaTest'
+        'mochaTest:unit',
+        'mochaTest:integration'
       ]);
     }
 
     else if (target === 'client') {
       return grunt.task.run([
         'clean:server',
-        'env:all',<% if(filters.stylus) { %>
-        'injector:stylus', <% } %><% if(filters.less) { %>
-        'injector:less', <% } %><% if(filters.sass) { %>
-        'injector:sass', <% } %>
+        'env:all',<% if (filters.stylus) { %>
+        'injector:stylus',<% } if (filters.less) { %>
+        'injector:less',<% } if (filters.sass) { %>
+        'injector:sass',<% } %>
         'concurrent:test',
         'injector',
-        'autoprefixer',
+        'postcss',
+        'wiredep:test',
         'karma'
       ]);
     }
 
     else if (target === 'e2e') {
-      return grunt.task.run([
-        'clean:server',
-        'env:all',
-        'env:test',<% if(filters.stylus) { %>
-        'injector:stylus', <% } %><% if(filters.less) { %>
-        'injector:less', <% } %><% if(filters.sass) { %>
-        'injector:sass', <% } %>
-        'concurrent:test',
-        'injector',
-        'wiredep',
-        'autoprefixer',
-        'express:dev',
-        'protractor'
-      ]);
+
+      if (option === 'prod') {
+        return grunt.task.run([
+          'build',
+          'env:all',
+          'env:prod',
+          'express:prod',
+          'protractor'
+        ]);
+      }
+
+      else {
+        return grunt.task.run([
+          'clean:server',
+          'env:all',
+          'env:test',<% if (filters.stylus) { %>
+          'injector:stylus',<% } if (filters.less) { %>
+          'injector:less',<% } if (filters.sass) { %>
+          'injector:sass',<% } %>
+          'concurrent:test',
+          'injector',
+          'wiredep:client',
+          'postcss',
+          'express:dev',
+          'protractor'
+        ]);
+      }
+    }
+
+    else if (target === 'coverage') {
+
+      if (option === 'unit') {
+        return grunt.task.run([
+          'env:all',
+          'env:test',
+          'mocha_istanbul:unit'
+        ]);
+      }
+
+      else if (option === 'integration') {
+        return grunt.task.run([
+          'env:all',
+          'env:test',
+          'mocha_istanbul:integration'
+        ]);
+      }
+
+      else if (option === 'check') {
+        return grunt.task.run([
+          'istanbul_check_coverage'
+        ]);
+      }
+
+      else {
+        return grunt.task.run([
+          'env:all',
+          'env:test',
+          'mocha_istanbul',
+          'istanbul_check_coverage'
+        ]);
+      }
+
     }
 
     else grunt.task.run([
@@ -848,15 +888,15 @@ module.exports = function (grunt) {
   });
 
   grunt.registerTask('build', [
-    'clean:dist',<% if(filters.stylus) { %>
-    'injector:stylus', <% } %><% if(filters.less) { %>
-    'injector:less', <% } %><% if(filters.sass) { %>
-    'injector:sass', <% } %>
+    'clean:dist',<% if (filters.stylus) { %>
+    'injector:stylus',<% } if (filters.less) { %>
+    'injector:less',<% } if (filters.sass) { %>
+    'injector:sass',<% } %>
     'concurrent:dist',
     'injector',
-    'wiredep',
+    'wiredep:client',
     'useminPrepare',
-    'autoprefixer',
+    'postcss',
     'ngtemplates',
     'concat',
     'ngAnnotate',
@@ -864,7 +904,7 @@ module.exports = function (grunt) {
     'cdnify',
     'cssmin',
     'uglify',
-    'rev',
+    'filerev',
     'usemin'
   ]);
 
diff --git a/app/templates/README.md b/app/templates/README.md
new file mode 100644
index 000000000..958834120
--- /dev/null
+++ b/app/templates/README.md
@@ -0,0 +1,34 @@
+# <%= _.slugify(_.humanize(appname)) %>
+
+This project was generated with the [Angular Full-Stack Generator](https://github.com/DaftMonk/generator-angular-fullstack) version <%= pkg.version %>.
+
+## Getting Started
+
+### Prerequisites
+
+- [Git](https://git-scm.com/)
+- [Node.js and NPM](nodejs.org) >= v0.12.0
+- [Bower](bower.io) (`npm install --global bower`)<% if(filters.sass) { %>
+- [Ruby](https://www.ruby-lang.org) and then `gem install sass`<% } if(filters.grunt) { %>
+- [Grunt](http://gruntjs.com/) (`npm install --global grunt-cli`)<% } if(filters.gulp) { %>
+- [Gulp](http://gulpjs.com/) (`npm install --global gulp`)<% } if(filters.mongoose) { %>
+- [MongoDB](https://www.mongodb.org/) - Keep a running daemon with `mongod`<% } if(filters.sequelize) { %>
+- [SQLite](https://www.sqlite.org/quickstart.html)<% } %>
+
+### Developing<% var i = 1; %>
+
+<%= i++ %>. Run `npm install` to install server dependencies.
+
+<%= i++ %>. Run `bower install` to install front-end dependencies.<% if(filters.mongoose) { %>
+
+<%= i++ %>. Run `mongod` in a separate shell to keep an instance of the MongoDB Daemon running<% } %>
+
+<%= i++ %>. Run <% if(filters.grunt) { %>`grunt serve`<% } if(filters.grunt && filters.gulp) { %> or <% } if(filters.gulp) { %>`gulp serve`<% } %> to start the development server. It should automatically open the client in your browser when ready.
+
+## Build & development
+
+Run `grunt build` for building and `grunt serve` for preview.
+
+## Testing
+
+Running `npm test` will run the unit tests with karma.
diff --git a/app/templates/_.gitignore b/app/templates/_.gitignore
index a5f8174b5..d5ae65fb7 100644
--- a/app/templates/_.gitignore
+++ b/app/templates/_.gitignore
@@ -1,6 +1,6 @@
 node_modules
 public
-.tmp<% if(filters.sass) { %>
+.tmp<% if (filters.sass) { %>
 .sass-cache<% } %>
 .idea
 client/bower_components
diff --git a/app/templates/_bower.json b/app/templates/_bower.json
index 156d04b32..1d41b39d1 100644
--- a/app/templates/_bower.json
+++ b/app/templates/_bower.json
@@ -2,23 +2,22 @@
   "name": "<%= _.slugify(_.humanize(appname)) %>",
   "version": "0.0.0",
   "dependencies": {
-    "angular": ">=1.2.*",
+    "angular": "~1.4.0",
     "json3": "~3.3.1",
     "es5-shim": "~3.0.1",<% if(filters.bootstrap) { %><% if (filters.sass) { %>
     "bootstrap-sass-official": "~3.1.1",<% } %>
     "bootstrap": "~3.1.1",<% } %>
-    "angular-resource": ">=1.2.*",
-    "angular-cookies": ">=1.2.*",
-    "angular-sanitize": ">=1.2.*",<% if(filters.ngroute) { %>
-    "angular-route": ">=1.2.*",<% } %><% if(filters.uibootstrap) { %>
-    "angular-bootstrap": "~0.11.0",<% } %>
+    "angular-resource": "~1.4.0",
+    "angular-cookies": "~1.4.0",
+    "angular-sanitize": "~1.4.0",<% if (filters.ngroute) { %>
+    "angular-route": "~1.4.0",<% } %><% if (filters.uibootstrap) { %>
+    "angular-bootstrap": "~0.13.0",<% } %>
     "font-awesome": ">=4.1.0",
     "lodash": "~2.4.1"<% if(filters.socketio) { %>,
-    "angular-socket-io": "~0.6.0"<% } %><% if(filters.uirouter) { %>,
+    "angular-socket-io": "~0.7.0"<% } %><% if(filters.uirouter) { %>,
     "angular-ui-router": "~0.2.15"<% } %>
   },
   "devDependencies": {
-    "angular-mocks": ">=1.2.*",
-    "angular-scenario": ">=1.2.*"
+    "angular-mocks": "~1.4.0"
   }
 }
diff --git a/app/templates/_package.json b/app/templates/_package.json
index 88541b924..53b4e9824 100644
--- a/app/templates/_package.json
+++ b/app/templates/_package.json
@@ -7,94 +7,110 @@
     "morgan": "~1.0.0",
     "body-parser": "~1.5.0",
     "method-override": "~1.0.0",
-    "serve-favicon": "~2.0.1",
     "cookie-parser": "~1.0.1",
     "express-session": "~1.0.2",
     "errorhandler": "~1.0.0",
     "compression": "~1.0.1",
-    "lodash": "~2.4.1",<% if(filters.jade) { %>
-    "jade": "~1.2.0",<% } %><% if(filters.html) { %>
-    "ejs": "~0.8.4",<% } %><% if(filters.mongoose) { %>
-    "mongoose": "~4.0.3",<% } %><% if(filters.auth) { %>
+    "composable-middleware": "^0.3.0",
+    "lodash": "~2.4.1",
+    "babel-core": "^5.6.4",<% if (filters.jade) { %>
+    "jade": "~1.2.0",<% } %><% if (filters.html) { %>
+    "ejs": "~0.8.4",<% } %><% if (filters.mongoose) { %>
+    "mongoose": "^4.1.2",
+    "bluebird": "^2.9.34",
+    "connect-mongo": "^0.8.1",<% } %><% if (filters.sequelize) { %>
+    "sequelize": "^3.5.1",
+    "sqlite3": "~3.0.2",
+    "express-sequelize-session": "0.4.0",<% } %><% if (filters.auth) { %>
     "jsonwebtoken": "^5.0.0",
     "express-jwt": "^3.0.0",
     "passport": "~0.2.0",
-    "passport-local": "~0.1.6",<% } %><% if(filters.facebookAuth) { %>
-    "passport-facebook": "latest",<% } %><% if(filters.twitterAuth) { %>
-    "passport-twitter": "latest",<% } %><% if(filters.googleAuth) { %>
-    "passport-google-oauth": "latest",<% } %>
-    "composable-middleware": "^0.3.0",
-    "connect-mongo": "^0.8.1"<% if(filters.socketio) { %>,
-    "socket.io": "^1.0.6",
-    "socket.io-client": "^1.0.6",
-    "socketio-jwt": "^3.0.0"<% } %>
+    "passport-local": "~0.1.6",<% } %><% if (filters.facebookAuth) { %>
+    "passport-facebook": "latest",<% } %><% if (filters.twitterAuth) { %>
+    "passport-twitter": "latest",<% } %><% if (filters.googleAuth) { %>
+    "passport-google-oauth": "latest",<% } %><% if (filters.socketio) { %>
+    "socket.io": "^1.3.5",
+    "socket.io-client": "^1.3.5",
+    "socketio-jwt": "^4.2.0",<% } %>
+    "serve-favicon": "~2.0.1"
   },
   "devDependencies": {
-    "grunt": "~0.4.4",
-    "grunt-autoprefixer": "~0.7.2",
-    "grunt-wiredep": "~1.8.0",
-    "grunt-concurrent": "~0.5.0",
-    "grunt-contrib-clean": "~0.5.0",
-    "grunt-contrib-concat": "~0.4.0",
-    "grunt-contrib-copy": "~0.5.0",
-    "grunt-contrib-cssmin": "~0.9.0",
-    "grunt-contrib-htmlmin": "~0.2.0",
-    "grunt-contrib-imagemin": "~0.7.1",
-    "grunt-contrib-jshint": "~0.10.0",
-    "grunt-contrib-uglify": "~0.4.0",
-    "grunt-contrib-watch": "~0.6.1",<% if(filters.coffee) { %>
-    "grunt-contrib-coffee": "^0.10.1",<% } %><% if(filters.jade) { %>
-    "grunt-contrib-jade": "^0.11.0",<% } %><% if(filters.less) { %>
-    "grunt-contrib-less": "^0.11.0",<% } %><% if(filters.babel) { %>
+    "autoprefixer-core": "^5.2.1",
+    "grunt": "~0.4.5",
+    "grunt-wiredep": "^2.0.0",
+    "grunt-concurrent": "^2.0.1",
+    "grunt-contrib-clean": "^0.6.0",
+    "grunt-contrib-concat": "^0.5.1",
+    "grunt-contrib-copy": "^0.8.0",
+    "grunt-contrib-cssmin": "^0.13.0",
+    "grunt-contrib-imagemin": "^0.9.4",
+    "grunt-contrib-jshint": "~0.11.2",
+    "grunt-contrib-uglify": "^0.9.1",
+    "grunt-contrib-watch": "~0.6.1",<% if (filters.coffee) { %>
+    "grunt-contrib-coffee": "^0.13.0",<% } %><% if (filters.jade) { %>
+    "grunt-contrib-jade": "^0.15.0",<% } %><% if (filters.less) { %>
+    "grunt-contrib-less": "^1.0.0",<% } %><% if(filters.babel) { %>
     "karma-babel-preprocessor": "^5.2.1",
     "grunt-babel": "~5.0.0",<% } %>
     "grunt-google-cdn": "~0.4.0",
-    "grunt-newer": "~0.7.0",
-    "grunt-ng-annotate": "^0.2.3",
-    "grunt-rev": "~0.1.0",
-    "grunt-svgmin": "~0.4.0",
-    "grunt-usemin": "~2.1.1",
+    "grunt-jscs": "^2.0.0",
+    "grunt-newer": "^1.1.1",
+    "grunt-ng-annotate": "^1.0.1",
+    "grunt-filerev": "^2.3.1",
+    "grunt-usemin": "^3.0.0",
     "grunt-env": "~0.4.1",
-    "grunt-node-inspector": "~0.1.5",
-    "grunt-nodemon": "~0.2.0",
+    "grunt-node-inspector": "^0.2.0",
+    "grunt-nodemon": "^0.4.0",
     "grunt-angular-templates": "^0.5.4",
     "grunt-dom-munger": "^3.4.0",
-    "grunt-protractor-runner": "^1.1.0",
-    "grunt-injector": "~0.5.4",
-    "grunt-karma": "~0.8.2",
-    "grunt-build-control": "~0.4.0",
-    "grunt-mocha-test": "~0.10.2",<% if(filters.sass) { %>
-    "grunt-contrib-sass": "^0.7.3",<% } %><% if(filters.stylus) { %>
+    "grunt-protractor-runner": "^2.0.0",
+    "grunt-injector": "^0.6.0",
+    "grunt-karma": "~0.12.0",
+    "grunt-build-control": "^0.5.0",<% if(filters.sass) { %>
+    "grunt-contrib-sass": "^0.9.0",<% } %><% if(filters.stylus) { %>
     "grunt-contrib-stylus": "latest",<% } %>
-    "jit-grunt": "^0.5.0",
-    "time-grunt": "~0.3.1",
-    "grunt-express-server": "~0.4.17",
+    "jit-grunt": "^0.9.1",
+    "time-grunt": "^1.2.1",
+    "grunt-express-server": "^0.5.1",
+    "grunt-postcss": "^0.5.5",
     "grunt-open": "~0.2.3",
     "open": "~0.0.4",
-    "jshint-stylish": "~0.1.5",
-    "connect-livereload": "~0.4.0",
+    "jshint-stylish": "~2.0.1",
+    "connect-livereload": "^0.5.3",
+    "mocha": "^2.2.5",
+    "grunt-mocha-test": "~0.12.7",
+    "grunt-mocha-istanbul": "^3.0.1",
+    "istanbul": "^0.3.17",
+    "chai-as-promised": "^5.1.0",
+    "chai-things": "^0.2.0",
+    "sinon-chai": "^2.8.0",<% if (filters.mocha) { %>
+    "karma-mocha": "^0.2.0",
+    "karma-chai-plugins": "^0.6.0",<% } if (filters.jasmine) { %>
+    "jasmine-core": "^2.3.4",
+    "karma-jasmine": "~0.3.0",
+    "jasmine-spec-reporter": "^2.4.0",<% } %>
     "karma-ng-scenario": "~0.1.0",
-    "karma-firefox-launcher": "~0.1.3",
+    "karma-firefox-launcher": "~0.1.6",
     "karma-script-launcher": "~0.1.0",
     "karma-html2js-preprocessor": "~0.1.0",
-    "karma-ng-jade2js-preprocessor": "^0.1.2",
-    "karma-jasmine": "~0.1.5",
-    "karma-chrome-launcher": "~0.1.3",
+    "karma-ng-jade2js-preprocessor": "^0.2.0",
+    "karma-chrome-launcher": "~0.2.0",
     "requirejs": "~2.1.11",
-    "karma-requirejs": "~0.2.1",
-    "karma-coffee-preprocessor": "~0.2.1",
+    "karma-requirejs": "~0.2.2",
+    "karma-coffee-preprocessor": "~0.3.0",
     "karma-jade-preprocessor": "0.0.11",
-    "karma-phantomjs-launcher": "~0.1.4",
-    "karma": "~0.12.9",
-    "karma-ng-html2js-preprocessor": "~0.1.0",
-    "supertest": "~0.11.0",
-    "should": "~3.3.1"
+    "karma-phantomjs-launcher": "~0.2.0",
+    "karma": "~0.13.3",
+    "karma-ng-html2js-preprocessor": "~0.1.2",
+    "karma-spec-reporter": "~0.0.20",
+    "proxyquire": "^1.0.1",
+    "supertest": "~0.11.0"
   },
   "engines": {
-    "node": ">=0.10.0"
+    "node": ">=0.12.0"
   },
   "scripts": {
-    "start": "node server/app.js",
+    "start": "node server",
     "test": "grunt test",
     "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
   },
diff --git a/app/templates/client/app/account(auth)/account(coffee).coffee b/app/templates/client/app/account(auth)/account(coffee).coffee
index 2b7b8b23b..c794d7f04 100644
--- a/app/templates/client/app/account(auth)/account(coffee).coffee
+++ b/app/templates/client/app/account(auth)/account(coffee).coffee
@@ -1,12 +1,20 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-<% if(filters.ngroute) { %>.config ($routeProvider) ->
+<% if (filters.ngroute) { %>.config ($routeProvider) ->
   $routeProvider
   .when '/login',
     templateUrl: 'app/account/login/login.html'
     controller: 'LoginCtrl'
 
+  .when '/logout',
+    name: 'logout'
+    referrer: '/'
+    controller: ($location, $route, Auth) ->
+      referrer = $route.current.params.referrer or $route.current.referrer or "/"
+      Auth.logout()
+      $location.path referrer
+
   .when '/signup',
     templateUrl: 'app/account/signup/signup.html'
     controller: 'SignupCtrl'
@@ -15,13 +23,25 @@ angular.module '<%= scriptAppName %>'
     templateUrl: 'app/account/settings/settings.html'
     controller: 'SettingsCtrl'
     authenticate: true
-<% } %><% if(filters.uirouter) { %>.config ($stateProvider) ->
+
+.run ($rootScope) ->
+  $rootScope.$on '$routeChangeStart', (event, next, current) ->
+    next.referrer = current.originalPath  if next.name is "logout" and current and current.originalPath and not current.authenticate
+<% } %><% if (filters.uirouter) { %>.config ($stateProvider) ->
   $stateProvider
   .state 'login',
     url: '/login'
     templateUrl: 'app/account/login/login.html'
     controller: 'LoginCtrl'
 
+  .state 'logout',
+    url: '/logout?referrer'
+    referrer: 'main'
+    controller: ($state, Auth) ->
+      referrer = $state.params.referrer or $state.current.referrer or "main"
+      Auth.logout()
+      $state.go referrer
+
   .state 'signup',
     url: '/signup'
     templateUrl: 'app/account/signup/signup.html'
@@ -32,4 +52,8 @@ angular.module '<%= scriptAppName %>'
     templateUrl: 'app/account/settings/settings.html'
     controller: 'SettingsCtrl'
     authenticate: true
-<% } %>
\ No newline at end of file
+
+.run ($rootScope) ->
+  $rootScope.$on '$stateChangeStart', (event, next, nextParams, current) ->
+    next.referrer = current.name  if next.name is "logout" and current and current.name and not current.authenticate
+<% } %>
diff --git a/app/templates/client/app/account(auth)/account(js).js b/app/templates/client/app/account(auth)/account(js).js
index 0e30543a5..d60fd72fe 100644
--- a/app/templates/client/app/account(auth)/account(js).js
+++ b/app/templates/client/app/account(auth)/account(js).js
@@ -1,12 +1,24 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  <% if(filters.ngroute) { %>.config(function ($routeProvider) {
+  <% if (filters.ngroute) { %>.config(function($routeProvider) {
     $routeProvider
       .when('/login', {
         templateUrl: 'app/account/login/login.html',
         controller: 'LoginCtrl'
       })
+      .when('/logout', {
+        name: 'logout',
+        referrer: '/',
+        template: '',
+        controller: function($location, $route, Auth) {
+          var referrer = $route.current.params.referrer ||
+                          $route.current.referrer ||
+                          '/';
+          Auth.logout();
+          $location.path(referrer);
+        }
+      })
       .when('/signup', {
         templateUrl: 'app/account/signup/signup.html',
         controller: 'SignupCtrl'
@@ -16,13 +28,32 @@ angular.module('<%= scriptAppName %>')
         controller: 'SettingsCtrl',
         authenticate: true
       });
-  });<% } %><% if(filters.uirouter) { %>.config(function ($stateProvider) {
+  })
+  .run(function($rootScope) {
+    $rootScope.$on('$routeChangeStart', function(event, next, current) {
+      if (next.name === 'logout' && current && current.originalPath && !current.authenticate) {
+        next.referrer = current.originalPath;
+      }
+    });
+  });<% } %><% if (filters.uirouter) { %>.config(function($stateProvider) {
     $stateProvider
       .state('login', {
         url: '/login',
         templateUrl: 'app/account/login/login.html',
         controller: 'LoginCtrl'
       })
+      .state('logout', {
+        url: '/logout?referrer',
+        referrer: 'main',
+        template: '',
+        controller: function($state, Auth) {
+          var referrer = $state.params.referrer ||
+                          $state.current.referrer ||
+                          'main';
+          Auth.logout();
+          $state.go(referrer);
+        }
+      })
       .state('signup', {
         url: '/signup',
         templateUrl: 'app/account/signup/signup.html',
@@ -34,4 +65,11 @@ angular.module('<%= scriptAppName %>')
         controller: 'SettingsCtrl',
         authenticate: true
       });
-  });<% } %>
\ No newline at end of file
+  })
+  .run(function($rootScope) {
+    $rootScope.$on('$stateChangeStart', function(event, next, nextParams, current) {
+      if (next.name === 'logout' && current && current.name && !current.authenticate) {
+        next.referrer = current.name;
+      }
+    });
+  });<% } %>
diff --git a/app/templates/client/app/account(auth)/login/login(html).html b/app/templates/client/app/account(auth)/login/login(html).html
index 572f2e144..667ecdd0e 100644
--- a/app/templates/client/app/account(auth)/login/login(html).html
+++ b/app/templates/client/app/account(auth)/login/login(html).html
@@ -1,4 +1,4 @@
-<div ng-include="'components/navbar/navbar.html'"></div>
+<navbar></navbar>
 
 <div class="container">
   <div class="row">
@@ -37,19 +37,19 @@ <h1>Login</h1>
           <button class="btn btn-inverse btn-lg btn-login" type="submit">
             Login
           </button>
-          <a class="btn btn-default btn-lg btn-register" href="/signup">
+          <a class="btn btn-default btn-lg btn-register" <% if (filters.uirouter) { %>ui-sref="signup"<% } else { %>href="/signup"<% } %>>
             Register
           </a>
         </div>
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
         <hr>
-        <div><% if(filters.facebookAuth) {%>
+        <div><% if (filters.facebookAuth) {%>
           <a class="btn btn-facebook" href="" ng-click="loginOauth('facebook')">
             <i class="fa fa-facebook"></i> Connect with Facebook
-          </a><% } %><% if(filters.googleAuth) {%>
+          </a><% } %><% if (filters.googleAuth) {%>
           <a class="btn btn-google-plus" href="" ng-click="loginOauth('google')">
             <i class="fa fa-google-plus"></i> Connect with Google+
-          </a><% } %><% if(filters.twitterAuth) {%>
+          </a><% } %><% if (filters.twitterAuth) {%>
           <a class="btn btn-twitter" href="" ng-click="loginOauth('twitter')">
             <i class="fa fa-twitter"></i> Connect with Twitter
           </a><% } %>
diff --git a/app/templates/client/app/account(auth)/login/login(jade).jade b/app/templates/client/app/account(auth)/login/login(jade).jade
index 4b13c0b13..e7ce91916 100644
--- a/app/templates/client/app/account(auth)/login/login(jade).jade
+++ b/app/templates/client/app/account(auth)/login/login(jade).jade
@@ -1,4 +1,4 @@
-div(ng-include='"components/navbar/navbar.html"')
+navbar
 .container
   .row
     .col-sm-12
@@ -20,7 +20,7 @@ div(ng-include='"components/navbar/navbar.html"')
       form.form(name='form', ng-submit='login(form)', novalidate='')
         .form-group
           label Email
-          input.form-control(type='text', name='email', ng-model='user.email')
+          input.form-control(type='email', name='email', ng-model='user.email')
         .form-group
           label Password
           input.form-control(type='password', name='password', ng-model='user.password')
@@ -34,20 +34,20 @@ div(ng-include='"components/navbar/navbar.html"')
           button.btn.btn-inverse.btn-lg.btn-login(type='submit')
             | Login
           = ' '
-          a.btn.btn-default.btn-lg.btn-register(href='/signup')
+          a.btn.btn-default.btn-lg.btn-register(<% if (filters.uirouter) { %>ui-sref='signup'<% } else { %>href='/signup'<% } %>)
             | Register
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
         hr
 
-        div<% if(filters.facebookAuth) {%>
+        div<% if (filters.facebookAuth) {%>
           a.btn.btn-facebook(href='', ng-click='loginOauth("facebook")')
             i.fa.fa-facebook
             |  Connect with Facebook
-          = ' '<% } %><% if(filters.googleAuth) {%>
+          = ' '<% } %><% if (filters.googleAuth) {%>
           a.btn.btn-google-plus(href='', ng-click='loginOauth("google")')
             i.fa.fa-google-plus
             |  Connect with Google+
-          = ' '<% } %><% if(filters.twitterAuth) {%>
+          = ' '<% } %><% if (filters.twitterAuth) {%>
           a.btn.btn-twitter(href='', ng-click='loginOauth("twitter")')
             i.fa.fa-twitter
             |  Connect with Twitter<% } %><% } %>
diff --git a/app/templates/client/app/account(auth)/login/login(less).less b/app/templates/client/app/account(auth)/login/login(less).less
index bd01a056e..6eaecd90c 100644
--- a/app/templates/client/app/account(auth)/login/login(less).less
+++ b/app/templates/client/app/account(auth)/login/login(less).less
@@ -1,4 +1,4 @@
-<% if(filters.bootstrap) { %>// Colors
+<% if (filters.bootstrap) { %>// Colors
 // --------------------------------------------------
 
 @btnText:                           #fff;
diff --git a/app/templates/client/app/account(auth)/login/login(sass).scss b/app/templates/client/app/account(auth)/login/login(sass).scss
index eb214a8ca..5b6956124 100644
--- a/app/templates/client/app/account(auth)/login/login(sass).scss
+++ b/app/templates/client/app/account(auth)/login/login(sass).scss
@@ -1,4 +1,4 @@
-<% if(filters.bootstrap) { %>// Colors
+<% if (filters.bootstrap) { %>// Colors
 // --------------------------------------------------
 
 $btnText:                           #fff;
diff --git a/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee b/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee
index 3f90c25d7..7bcb69969 100644
--- a/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee
+++ b/app/templates/client/app/account(auth)/login/login.controller(coffee).coffee
@@ -1,7 +1,7 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-.controller 'LoginCtrl', ($scope, Auth, $location<% if(filters.oauth) {%>, $window<% } %>) ->
+.controller 'LoginCtrl', ($scope, Auth<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %><% if (filters.oauth) {%>, $window<% } %>) ->
   $scope.user = {}
   $scope.errors = {}
   $scope.login = (form) ->
@@ -14,10 +14,10 @@ angular.module '<%= scriptAppName %>'
         password: $scope.user.password
 
       .then ->
-        $location.path '/'
+        <% if (filters.ngroute) { %>$location.path '/'<% } %><% if (filters.uirouter) { %>$state.go 'main'<% } %>
 
       .catch (err) ->
         $scope.errors.other = err.message
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
   $scope.loginOauth = (provider) ->
     $window.location.href = '/auth/' + provider<% } %>
diff --git a/app/templates/client/app/account(auth)/login/login.controller(js).js b/app/templates/client/app/account(auth)/login/login.controller(js).js
index 7b13da384..2417e62f4 100644
--- a/app/templates/client/app/account(auth)/login/login.controller(js).js
+++ b/app/templates/client/app/account(auth)/login/login.controller(js).js
@@ -1,28 +1,28 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .controller('LoginCtrl', function ($scope, Auth, $location<% if (filters.oauth) { %>, $window<% } %>) {
+  .controller('LoginCtrl', function($scope, Auth<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %><% if (filters.oauth) { %>, $window<% } %>) {
     $scope.user = {};
     $scope.errors = {};
 
     $scope.login = function(form) {
       $scope.submitted = true;
 
-      if(form.$valid) {
+      if (form.$valid) {
         Auth.login({
           email: $scope.user.email,
           password: $scope.user.password
         })
-        .then( function() {
+        .then(function() {
           // Logged in, redirect to home
-          $location.path('/');
+          <% if (filters.ngroute) { %>$location.path('/');<% } %><% if (filters.uirouter) { %>$state.go('main');<% } %>
         })
-        .catch( function(err) {
+        .catch(function(err) {
           $scope.errors.other = err.message;
         });
       }
     };
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
     $scope.loginOauth = function(provider) {
       $window.location.href = '/auth/' + provider;
     };<% } %>
diff --git a/app/templates/client/app/account(auth)/settings/settings(html).html b/app/templates/client/app/account(auth)/settings/settings(html).html
index bb5d8ded0..ec4e2d820 100644
--- a/app/templates/client/app/account(auth)/settings/settings(html).html
+++ b/app/templates/client/app/account(auth)/settings/settings(html).html
@@ -1,4 +1,4 @@
-<div ng-include="'components/navbar/navbar.html'"></div>
+<navbar></navbar>
 
 <div class="container">
   <div class="row">
@@ -36,4 +36,4 @@ <h1>Change Password</h1>
       </form>
     </div>
   </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/app/templates/client/app/account(auth)/settings/settings(jade).jade b/app/templates/client/app/account(auth)/settings/settings(jade).jade
index 2dc55d402..96340b522 100644
--- a/app/templates/client/app/account(auth)/settings/settings(jade).jade
+++ b/app/templates/client/app/account(auth)/settings/settings(jade).jade
@@ -1,4 +1,4 @@
-div(ng-include='"components/navbar/navbar.html"')
+navbar
 .container
   .row
     .col-sm-12
diff --git a/app/templates/client/app/account(auth)/settings/settings.controller(js).js b/app/templates/client/app/account(auth)/settings/settings.controller(js).js
index 829bd8248..eeb1219cf 100644
--- a/app/templates/client/app/account(auth)/settings/settings.controller(js).js
+++ b/app/templates/client/app/account(auth)/settings/settings.controller(js).js
@@ -1,21 +1,21 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .controller('SettingsCtrl', function ($scope, User, Auth) {
+  .controller('SettingsCtrl', function($scope, User, Auth) {
     $scope.errors = {};
 
     $scope.changePassword = function(form) {
       $scope.submitted = true;
-      if(form.$valid) {
-        Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword )
-        .then( function() {
-          $scope.message = 'Password successfully changed.';
-        })
-        .catch( function() {
-          form.password.$setValidity('mongoose', false);
-          $scope.errors.other = 'Incorrect password';
-          $scope.message = '';
-        });
+      if (form.$valid) {
+        Auth.changePassword($scope.user.oldPassword, $scope.user.newPassword)
+          .then(function() {
+            $scope.message = 'Password successfully changed.';
+          })
+          .catch(function() {
+            form.password.$setValidity('mongoose', false);
+            $scope.errors.other = 'Incorrect password';
+            $scope.message = '';
+          });
       }
-		};
+    };
   });
diff --git a/app/templates/client/app/account(auth)/signup/signup(html).html b/app/templates/client/app/account(auth)/signup/signup(html).html
index 59faed568..5052a02ee 100644
--- a/app/templates/client/app/account(auth)/signup/signup(html).html
+++ b/app/templates/client/app/account(auth)/signup/signup(html).html
@@ -1,4 +1,4 @@
-<div ng-include="'components/navbar/navbar.html'"></div>
+<navbar></navbar>
 
 <div class="container">
   <div class="row">
@@ -55,22 +55,22 @@ <h1>Sign up</h1>
         </div>
 
         <div>
-          <button class="btn btn-inverse btn-lg btn-login" type="submit">
+          <button class="btn btn-inverse btn-lg btn-register" type="submit">
             Sign up
           </button>
-          <a class="btn btn-default btn-lg btn-register" href="/login">
+          <a class="btn btn-default btn-lg btn-login" <% if (filters.uirouter) { %>ui-sref="login"<% } else { %>href="/login"<% } %>>
             Login
           </a>
         </div>
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
         <hr>
-        <div><% if(filters.facebookAuth) {%>
+        <div><% if (filters.facebookAuth) {%>
           <a class="btn btn-facebook" href="" ng-click="loginOauth('facebook')">
             <i class="fa fa-facebook"></i> Connect with Facebook
-          </a><% } %><% if(filters.googleAuth) {%>
+          </a><% } %><% if (filters.googleAuth) {%>
           <a class="btn btn-google-plus" href="" ng-click="loginOauth('google')">
             <i class="fa fa-google-plus"></i> Connect with Google+
-          </a><% } %><% if(filters.twitterAuth) {%>
+          </a><% } %><% if (filters.twitterAuth) {%>
           <a class="btn btn-twitter" href="" ng-click="loginOauth('twitter')">
             <i class="fa fa-twitter"></i> Connect with Twitter
           </a><% } %>
diff --git a/app/templates/client/app/account(auth)/signup/signup(jade).jade b/app/templates/client/app/account(auth)/signup/signup(jade).jade
index 43815a21c..cca29a28e 100644
--- a/app/templates/client/app/account(auth)/signup/signup(jade).jade
+++ b/app/templates/client/app/account(auth)/signup/signup(jade).jade
@@ -1,4 +1,4 @@
-div(ng-include='"components/navbar/navbar.html"')
+navbar
 .container
   .row
     .col-sm-12
@@ -33,24 +33,24 @@ div(ng-include='"components/navbar/navbar.html"')
             | {{ errors.password }}
 
         div
-          button.btn.btn-inverse.btn-lg.btn-login(type='submit')
+          button.btn.btn-inverse.btn-lg.btn-register(type='submit')
             | Sign up
           = ' '
-          a.btn.btn-default.btn-lg.btn-register(href='/login')
+          a.btn.btn-default.btn-lg.btn-login(<% if (filters.uirouter) { %>ui-sref='login'<% } else { %>href='/login'<% } %>)
             | Login
 
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
         hr
 
-        div<% if(filters.facebookAuth) {%>
+        div<% if (filters.facebookAuth) {%>
           a.btn.btn-facebook(href='', ng-click='loginOauth("facebook")')
             i.fa.fa-facebook
             |  Connect with Facebook
-          = ' '<% } %><% if(filters.googleAuth) {%>
+          = ' '<% } %><% if (filters.googleAuth) {%>
           a.btn.btn-google-plus(href='', ng-click='loginOauth("google")')
             i.fa.fa-google-plus
             |  Connect with Google+
-          = ' '<% } %><% if(filters.twitterAuth) {%>
+          = ' '<% } %><% if (filters.twitterAuth) {%>
           a.btn.btn-twitter(href='', ng-click='loginOauth("twitter")')
             i.fa.fa-twitter
             |  Connect with Twitter<% } %><% } %>
diff --git a/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee b/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee
index 1b9c9696f..d167b7e30 100644
--- a/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee
+++ b/app/templates/client/app/account(auth)/signup/signup.controller(coffee).coffee
@@ -1,7 +1,7 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-.controller 'SignupCtrl', ($scope, Auth, $location<% if(filters.oauth) {%>, $window<% } %>) ->
+.controller 'SignupCtrl', ($scope, Auth<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %><% if (filters.oauth) {%>, $window<% } %>) ->
   $scope.user = {}
   $scope.errors = {}
   $scope.register = (form) ->
@@ -15,16 +15,22 @@ angular.module '<%= scriptAppName %>'
         password: $scope.user.password
 
       .then ->
-        $location.path '/'
+        <% if (filters.ngroute) { %>$location.path '/'<% } %><% if (filters.uirouter) { %>$state.go 'main'<% } %>
 
       .catch (err) ->
         err = err.data
         $scope.errors = {}
-
+<% if (filters.mongooseModels) { %>
         # Update validity of form fields that match the mongoose errors
         angular.forEach err.errors, (error, field) ->
           form[field].$setValidity 'mongoose', false
-          $scope.errors[field] = error.message
-<% if(filters.oauth) {%>
+          $scope.errors[field] = error.message<% }
+  if (filters.sequelizeModels) { %>
+        # Update validity of form fields that match the sequelize errors
+        if err.name
+          angular.forEach err.fields, (field) ->
+            form[field].$setValidity 'mongoose', false
+            $scope.errors[field] = err.message<% } %>
+<% if (filters.oauth) {%>
   $scope.loginOauth = (provider) ->
     $window.location.href = '/auth/' + provider<% } %>
diff --git a/app/templates/client/app/account(auth)/signup/signup.controller(js).js b/app/templates/client/app/account(auth)/signup/signup.controller(js).js
index 7d6ba3d38..346eb7ea7 100644
--- a/app/templates/client/app/account(auth)/signup/signup.controller(js).js
+++ b/app/templates/client/app/account(auth)/signup/signup.controller(js).js
@@ -1,36 +1,44 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .controller('SignupCtrl', function ($scope, Auth, $location<% if (filters.oauth) { %>, $window<% } %>) {
+  .controller('SignupCtrl', function($scope, Auth<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %><% if (filters.oauth) { %>, $window<% } %>) {
     $scope.user = {};
     $scope.errors = {};
 
     $scope.register = function(form) {
       $scope.submitted = true;
 
-      if(form.$valid) {
+      if (form.$valid) {
         Auth.createUser({
           name: $scope.user.name,
           email: $scope.user.email,
           password: $scope.user.password
         })
-        .then( function() {
+        .then(function() {
           // Account created, redirect to home
-          $location.path('/');
+          <% if (filters.ngroute) { %>$location.path('/');<% } %><% if (filters.uirouter) { %>$state.go('main');<% } %>
         })
-        .catch( function(err) {
+        .catch(function(err) {
           err = err.data;
           $scope.errors = {};
-
+<% if (filters.mongooseModels) { %>
           // Update validity of form fields that match the mongoose errors
           angular.forEach(err.errors, function(error, field) {
             form[field].$setValidity('mongoose', false);
             $scope.errors[field] = error.message;
-          });
+          });<% }
+  if (filters.sequelizeModels) { %>
+          // Update validity of form fields that match the sequelize errors
+          if (err.name) {
+            angular.forEach(err.fields, function(field) {
+              form[field].$setValidity('mongoose', false);
+              $scope.errors[field] = err.message;
+            });
+          }<% } %>
         });
       }
     };
-<% if(filters.oauth) {%>
+<% if (filters.oauth) {%>
     $scope.loginOauth = function(provider) {
       $window.location.href = '/auth/' + provider;
     };<% } %>
diff --git a/app/templates/client/app/admin(auth)/admin(coffee).coffee b/app/templates/client/app/admin(auth)/admin(coffee).coffee
index a0497445e..99b49177f 100644
--- a/app/templates/client/app/admin(auth)/admin(coffee).coffee
+++ b/app/templates/client/app/admin(auth)/admin(coffee).coffee
@@ -1,15 +1,15 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-<% if(filters.ngroute) { %>.config ($routeProvider) ->
+<% if (filters.ngroute) { %>.config ($routeProvider) ->
   $routeProvider
   .when '/admin',
     templateUrl: 'app/admin/admin.html'
     controller: 'AdminCtrl'
-<% } %><% if(filters.uirouter) { %>.config ($stateProvider) ->
+<% } %><% if (filters.uirouter) { %>.config ($stateProvider) ->
   $stateProvider
   .state 'admin',
     url: '/admin'
     templateUrl: 'app/admin/admin.html'
     controller: 'AdminCtrl'
-<% } %>
\ No newline at end of file
+<% } %>
diff --git a/app/templates/client/app/admin(auth)/admin(html).html b/app/templates/client/app/admin(auth)/admin(html).html
index 5c27c7af2..7688c9b47 100644
--- a/app/templates/client/app/admin(auth)/admin(html).html
+++ b/app/templates/client/app/admin(auth)/admin(html).html
@@ -1,4 +1,4 @@
-<div ng-include="'components/navbar/navbar.html'"></div>
+<navbar></navbar>
 
 <div class="container">
   <p>The delete user and user index api routes are restricted to users with the 'admin' role.</p>
@@ -9,4 +9,4 @@
         <a ng-click="delete(user)" class="trash"><span class="glyphicon glyphicon-trash pull-right"></span></a>
     </li>
   </ul>
-</div>
\ No newline at end of file
+</div>
diff --git a/app/templates/client/app/admin(auth)/admin(jade).jade b/app/templates/client/app/admin(auth)/admin(jade).jade
index fd80a0bb6..bcef64773 100644
--- a/app/templates/client/app/admin(auth)/admin(jade).jade
+++ b/app/templates/client/app/admin(auth)/admin(jade).jade
@@ -1,4 +1,4 @@
-div(ng-include='"components/navbar/navbar.html"')
+navbar
 .container
   p
     | The delete user and user index api routes are restricted to users with the 'admin' role.
@@ -8,4 +8,4 @@ div(ng-include='"components/navbar/navbar.html"')
       br
       span.text-muted {{user.email}}
       a.trash(ng-click='delete(user)')
-        span.glyphicon.glyphicon-trash.pull-right
\ No newline at end of file
+        span.glyphicon.glyphicon-trash.pull-right
diff --git a/app/templates/client/app/admin(auth)/admin(js).js b/app/templates/client/app/admin(auth)/admin(js).js
index 270e8a974..f37ba9fcc 100644
--- a/app/templates/client/app/admin(auth)/admin(js).js
+++ b/app/templates/client/app/admin(auth)/admin(js).js
@@ -1,17 +1,17 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  <% if(filters.ngroute) { %>.config(function ($routeProvider) {
+  <% if (filters.ngroute) { %>.config(function($routeProvider) {
     $routeProvider
       .when('/admin', {
         templateUrl: 'app/admin/admin.html',
         controller: 'AdminCtrl'
       });
-  });<% } %><% if(filters.uirouter) { %>.config(function ($stateProvider) {
+  });<% } %><% if (filters.uirouter) { %>.config(function($stateProvider) {
     $stateProvider
       .state('admin', {
         url: '/admin',
         templateUrl: 'app/admin/admin.html',
         controller: 'AdminCtrl'
       });
-  });<% } %>
\ No newline at end of file
+  });<% } %>
diff --git a/app/templates/client/app/admin(auth)/admin(less).less b/app/templates/client/app/admin(auth)/admin(less).less
index ad8202750..a6f536dc5 100644
--- a/app/templates/client/app/admin(auth)/admin(less).less
+++ b/app/templates/client/app/admin(auth)/admin(less).less
@@ -1 +1 @@
-.trash { color:rgb(209, 91, 71); }
\ No newline at end of file
+.trash { color:rgb(209, 91, 71); }
diff --git a/app/templates/client/app/admin(auth)/admin(stylus).styl b/app/templates/client/app/admin(auth)/admin(stylus).styl
index d57e50db5..d7d50a172 100644
--- a/app/templates/client/app/admin(auth)/admin(stylus).styl
+++ b/app/templates/client/app/admin(auth)/admin(stylus).styl
@@ -1,2 +1,2 @@
 .trash
-    color rgb(209, 91, 71)
\ No newline at end of file
+    color rgb(209, 91, 71)
diff --git a/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee b/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee
index 7a16032da..5183df059 100644
--- a/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee
+++ b/app/templates/client/app/admin(auth)/admin.controller(coffee).coffee
@@ -9,4 +9,4 @@ angular.module '<%= scriptAppName %>'
 
   $scope.delete = (user) ->
     User.remove id: user._id
-    _.remove $scope.users, user
\ No newline at end of file
+    $scope.users.splice @$index, 1
diff --git a/app/templates/client/app/admin(auth)/admin.controller(js).js b/app/templates/client/app/admin(auth)/admin.controller(js).js
index dd6b09405..3cbfd4b7f 100644
--- a/app/templates/client/app/admin(auth)/admin.controller(js).js
+++ b/app/templates/client/app/admin(auth)/admin.controller(js).js
@@ -1,17 +1,13 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .controller('AdminCtrl', function ($scope, $http, Auth, User) {
+  .controller('AdminCtrl', function($scope, $http, Auth, User) {
 
     // Use the User $resource to fetch all users
     $scope.users = User.query();
 
     $scope.delete = function(user) {
       User.remove({ id: user._id });
-      angular.forEach($scope.users, function(u, i) {
-        if (u === user) {
-          $scope.users.splice(i, 1);
-        }
-      });
+      $scope.users.splice(this.$index, 1);
     };
   });
diff --git a/app/templates/client/app/app(coffee).coffee b/app/templates/client/app/app(coffee).coffee
index ea9ae3c95..f0c1bd129 100644
--- a/app/templates/client/app/app(coffee).coffee
+++ b/app/templates/client/app/app(coffee).coffee
@@ -1,39 +1,42 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>', [<%= angularModules %>]
-<% if(filters.ngroute) { %>.config ($routeProvider, $locationProvider<% if(filters.auth) { %>, $httpProvider<% } %>) ->
+<% if (filters.ngroute) { %>.config ($routeProvider, $locationProvider<% if (filters.auth) { %>, $httpProvider<% } %>) ->
   $routeProvider
   .otherwise
     redirectTo: '/'
 
-  $locationProvider.html5Mode true<% if(filters.auth) { %>
+  $locationProvider.html5Mode true<% if (filters.auth) { %>
   $httpProvider.interceptors.push 'authInterceptor'<% } %>
-<% } %><% if(filters.uirouter) { %>.config ($stateProvider, $urlRouterProvider, $locationProvider<% if(filters.auth) { %>, $httpProvider<% } %>) ->
+<% } %><% if (filters.uirouter) { %>.config ($stateProvider, $urlRouterProvider, $locationProvider<% if (filters.auth) { %>, $httpProvider<% } %>) ->
   $urlRouterProvider
   .otherwise '/'
 
-  $locationProvider.html5Mode true<% if(filters.auth) { %>
+  $locationProvider.html5Mode true<% if (filters.auth) { %>
   $httpProvider.interceptors.push 'authInterceptor'<% } %>
-<% } %><% if(filters.auth) { %>
-.factory 'authInterceptor', ($rootScope, $q, $cookieStore, $location) ->
-  # Add authorization token to headers
+<% } %><% if (filters.auth) { %>
+.factory 'authInterceptor', ($rootScope, $q, $cookies<% if (filters.ngroute) { %>, $location<% } if (filters.uirouter) { %>, $injector<% } %>) ->
+  <% if (filters.uirouter) { %>state = null
+  <% } %># Add authorization token to headers
   request: (config) ->
     config.headers = config.headers or {}
-    config.headers.Authorization = 'Bearer ' + $cookieStore.get 'token' if $cookieStore.get 'token'
+    config.headers.Authorization = 'Bearer ' + $cookies.get 'token' if $cookies.get 'token'
     config
 
   # Intercept 401s and redirect you to login
   responseError: (response) ->
     if response.status is 401
-      $location.path '/login'
+      <% if (filters.ngroute) { %>$location.path '/login'<% } if (filters.uirouter) { %>(state || state = $injector.get '$state').go 'login'<% } %>
       # remove any stale tokens
-      $cookieStore.remove 'token'
+      $cookies.remove 'token'
 
     $q.reject response
 
-.run ($rootScope, $location, Auth) ->
-  # Redirect to login if route requires auth and you're not logged in
-  $rootScope.$on <% if(filters.ngroute) { %>'$routeChangeStart'<% } %><% if(filters.uirouter) { %>'$stateChangeStart'<% } %>, (event, next) ->
-    Auth.isLoggedInAsync (loggedIn) ->
-      $location.path "/login" if next.authenticate and not loggedIn
-<% } %>
\ No newline at end of file
+.run ($rootScope<% if (filters.ngroute) { %>, $location<% } %><% if (filters.uirouter) { %>, $state<% } %>, Auth) ->
+  # Redirect to login if route requires auth and the user is not logged in
+  $rootScope.$on <% if (filters.ngroute) { %>'$routeChangeStart'<% } %><% if (filters.uirouter) { %>'$stateChangeStart'<% } %>, (event, next) ->
+    if next.authenticate
+      Auth.isLoggedIn (loggedIn) ->
+        if !loggedIn
+          event.preventDefault()
+          <% if (filters.ngroute) { %>$location.path '/login'<% } %><% if (filters.uirouter) { %>$state.go 'login'<% }} %>
diff --git a/app/templates/client/app/app(css).css b/app/templates/client/app/app(css).css
index f1a61a918..2dbd1e8c5 100644
--- a/app/templates/client/app/app(css).css
+++ b/app/templates/client/app/app(css).css
@@ -1,15 +1,15 @@
-<% if(filters.bootstrap) { %>
+<% if (filters.bootstrap) { %>
 /**
  * Bootstrap Fonts
  */
 
 @font-face {
-    font-family: 'Glyphicons Halflings';
-    src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot');
-    src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
-    url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'),
-    url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'),
-    url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+  font-family: 'Glyphicons Halflings';
+  src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot');
+  src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
+  url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'),
+  url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'),
+  url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
 }<% } %>
 
 /**
@@ -17,30 +17,30 @@
  */
 
 @font-face {
-    font-family: 'FontAwesome';
-    src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0');
-    src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),
-    url('../bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'),
-    url('../bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),
-    url('../bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');
-    font-weight: normal;
-    font-style: normal;
+  font-family: 'FontAwesome';
+  src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0');
+  src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),
+  url('../bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'),
+  url('../bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),
+  url('../bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');
+  font-weight: normal;
+  font-style: normal;
 }
 
 /**
  * App-wide Styles
  */
 
-.browsehappy {
-    margin: 0.2em 0;
-    background: #ccc;
-    color: #000;
-    padding: 0.2em 0;
+.browserupgrade {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0;
 }
-<% if (!filters.bootstrap) { %>
+<% if(!filters.bootstrap) { %>
 /* Responsive: Portrait tablets and up */
 @media screen and (min-width: 768px) {
-    .container {
-        max-width: 730px;
-    }
-}<% } %>
\ No newline at end of file
+  .container {
+    max-width: 730px;
+  }
+}<% } %>
diff --git a/app/templates/client/app/app(js).js b/app/templates/client/app/app(js).js
index c8850ed07..27410af8a 100644
--- a/app/templates/client/app/app(js).js
+++ b/app/templates/client/app/app(js).js
@@ -1,39 +1,40 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>', [<%= angularModules %>])
-  <% if(filters.ngroute) { %>.config(function ($routeProvider, $locationProvider<% if(filters.auth) { %>, $httpProvider<% } %>) {
+  <% if (filters.ngroute) { %>.config(function($routeProvider, $locationProvider<% if (filters.auth) { %>, $httpProvider<% } %>) {
     $routeProvider
       .otherwise({
         redirectTo: '/'
       });
 
-    $locationProvider.html5Mode(true);<% if(filters.auth) { %>
+    $locationProvider.html5Mode(true);<% if (filters.auth) { %>
     $httpProvider.interceptors.push('authInterceptor');<% } %>
-  })<% } %><% if(filters.uirouter) { %>.config(function ($stateProvider, $urlRouterProvider, $locationProvider<% if(filters.auth) { %>, $httpProvider<% } %>) {
+  })<% } if (filters.uirouter) { %>.config(function($stateProvider, $urlRouterProvider, $locationProvider<% if (filters.auth) { %>, $httpProvider<% } %>) {
     $urlRouterProvider
       .otherwise('/');
 
-    $locationProvider.html5Mode(true);<% if(filters.auth) { %>
+    $locationProvider.html5Mode(true);<% if (filters.auth) { %>
     $httpProvider.interceptors.push('authInterceptor');<% } %>
-  })<% } %><% if(filters.auth) { %>
+  })<% } if (filters.auth) { %>
 
-  .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
-    return {
+  .factory('authInterceptor', function($rootScope, $q, $cookies<% if (filters.ngroute) { %>, $location<% } if (filters.uirouter) { %>, $injector<% } %>) {
+    <% if (filters.uirouter) { %>var state;
+    <% } %>return {
       // Add authorization token to headers
-      request: function (config) {
+      request: function(config) {
         config.headers = config.headers || {};
-        if ($cookieStore.get('token')) {
-          config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
+        if ($cookies.get('token')) {
+          config.headers.Authorization = 'Bearer ' + $cookies.get('token');
         }
         return config;
       },
 
       // Intercept 401s and redirect you to login
       responseError: function(response) {
-        if(response.status === 401) {
-          $location.path('/login');
+        if (response.status === 401) {
+          <% if (filters.ngroute) { %>$location.path('/login');<% } if (filters.uirouter) { %>(state || (state = $injector.get('$state'))).go('login');<% } %>
           // remove any stale tokens
-          $cookieStore.remove('token');
+          $cookies.remove('token');
           return $q.reject(response);
         }
         else {
@@ -43,14 +44,16 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>])
     };
   })
 
-  .run(function ($rootScope, $location, Auth) {
-    // Redirect to login if route requires auth and you're not logged in
-    $rootScope.$on(<% if(filters.ngroute) { %>'$routeChangeStart'<% } %><% if(filters.uirouter) { %>'$stateChangeStart'<% } %>, function (event, next) {
-      Auth.isLoggedInAsync(function(loggedIn) {
-        if (next.authenticate && !loggedIn) {
-          event.preventDefault();
-          $location.path('/login');
-        }
-      });
+  .run(function($rootScope<% if (filters.ngroute) { %>, $location<% } if (filters.uirouter) { %>, $state<% } %>, Auth) {
+    // Redirect to login if route requires auth and the user is not logged in
+    $rootScope.$on(<% if (filters.ngroute) { %>'$routeChangeStart'<% } %><% if (filters.uirouter) { %>'$stateChangeStart'<% } %>, function(event, next) {
+      if (next.authenticate) {
+        Auth.isLoggedIn(function(loggedIn) {
+          if (!loggedIn) {
+            event.preventDefault();
+            <% if (filters.ngroute) { %>$location.path('/login');<% } if (filters.uirouter) { %>$state.go('login');<% } %>
+          }
+        });
+      }
     });
   })<% } %>;
diff --git a/app/templates/client/app/app(less).less b/app/templates/client/app/app(less).less
index 30639f539..cbfffbe88 100644
--- a/app/templates/client/app/app(less).less
+++ b/app/templates/client/app/app(less).less
@@ -1,29 +1,29 @@
-<% if(filters.bootstrap) { %>@import 'bootstrap/less/bootstrap.less';<% } %>
-@import 'font-awesome/less/font-awesome.less';
+<% if (filters.bootstrap) { %>@import '../bower_components/bootstrap/less/bootstrap.less';<% } %>
+@import '../bower_components/font-awesome/less/font-awesome.less';
 
-<% if(filters.bootstrap) { %>@icon-font-path: '/bower_components/bootstrap/fonts/';<% } %>
-@fa-font-path: '/bower_components/font-awesome/fonts';
+<% if (filters.bootstrap) { %>@icon-font-path: '../bower_components/bootstrap/fonts/';<% } %>
+@fa-font-path: '../bower_components/font-awesome/fonts';
 
 /**
  * App-wide Styles
  */
 
-.browsehappy {
-    margin: 0.2em 0;
-    background: #ccc;
-    color: #000;
-    padding: 0.2em 0;
+.browserupgrade {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0;
 }
-<% if (!filters.bootstrap) { %>
+<% if(!filters.bootstrap) { %>
 /* Responsive: Portrait tablets and up */
 @media screen and (min-width: 768px) {
-    .container {
-        max-width: 730px;
-    }
+  .container {
+    max-width: 730px;
+  }
 }
 <% } %>
 // injector
 @import 'account/login/login.less';
 @import 'admin/admin.less';
 @import 'main/main.less';
-// endinjector
\ No newline at end of file
+// endinjector
diff --git a/app/templates/client/app/app(sass).scss b/app/templates/client/app/app(sass).scss
index 4b8ae7a04..889878aee 100644
--- a/app/templates/client/app/app(sass).scss
+++ b/app/templates/client/app/app(sass).scss
@@ -1,25 +1,25 @@
-<% if(filters.bootstrap) { %>$icon-font-path: "/bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/";<% } %>
-$fa-font-path: "/bower_components/font-awesome/fonts";
-<% if(filters.bootstrap) { %>
-@import 'bootstrap-sass-official/vendor/assets/stylesheets/bootstrap';<% } %>
-@import 'font-awesome/scss/font-awesome';
+<% if (filters.bootstrap) { %>$icon-font-path: "../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/";<% } %>
+$fa-font-path: "../bower_components/font-awesome/fonts";
+<% if (filters.bootstrap) { %>
+@import '../bower_components/bootstrap-sass-official/vendor/assets/stylesheets/bootstrap';<% } %>
+@import '../bower_components/font-awesome/scss/font-awesome';
 
 /**
  * App-wide Styles
  */
 
-.browsehappy {
-    margin: 0.2em 0;
-    background: #ccc;
-    color: #000;
-    padding: 0.2em 0;
+.browserupgrade {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0;
 }
-<% if (!filters.bootstrap) { %>
+<% if(!filters.bootstrap) { %>
 /* Responsive: Portrait tablets and up */
 @media screen and (min-width: 768px) {
-    .container {
-        max-width: 730px;
-    }
+  .container {
+    max-width: 730px;
+  }
 }
 <% } %>
 // Component styles are injected through grunt
@@ -27,4 +27,4 @@ $fa-font-path: "/bower_components/font-awesome/fonts";
 @import 'account/login/login.scss';
 @import 'admin/admin.scss';
 @import 'main/main.scss';
-// endinjector
\ No newline at end of file
+// endinjector
diff --git a/app/templates/client/app/app(stylus).styl b/app/templates/client/app/app(stylus).styl
index b7e4bb9c1..d25cdfc59 100644
--- a/app/templates/client/app/app(stylus).styl
+++ b/app/templates/client/app/app(stylus).styl
@@ -1,17 +1,17 @@
-@import "font-awesome/css/font-awesome.css"
-<% if(filters.bootstrap) { %>@import "bootstrap/dist/css/bootstrap.css"
+@import "../bower_components/font-awesome/css/font-awesome.css"
+<% if (filters.bootstrap) { %>@import "../bower_components/bootstrap/dist/css/bootstrap.css"
 
 //
 // Bootstrap Fonts
 //
 
 @font-face
-    font-family: 'Glyphicons Halflings'
-    src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot')
-    src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
-    url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'),
-    url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'),
-    url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+  font-family: 'Glyphicons Halflings'
+  src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot')
+  src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
+  url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'),
+  url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'),
+  url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
 <% } %>
 //
 // Font Awesome Fonts
@@ -31,20 +31,20 @@
 // App-wide Styles
 //
 
-.browsehappy
-    background #ccc
-    color #000
-    margin 0.2em 0
-    padding 0.2em 0
-<% if (!filters.bootstrap) { %>
+.browserupgrade
+  background #ccc
+  color #000
+  margin 0.2em 0
+  padding 0.2em 0
+<% if(!filters.bootstrap) { %>
 // Responsive: Portrait tablets and up
 @media screen and (min-width: 768px)
-    .container
-        max-width 730px
+  .container
+    max-width 730px
 <% } %>
 // Component styles are injected through grunt
 // injector
 @import "account/login/login"
 @import "admin/admin"
 @import "main/main"
-// endinjector
\ No newline at end of file
+// endinjector
diff --git a/app/templates/client/app/main/main(coffee).coffee b/app/templates/client/app/main/main(coffee).coffee
index 6d84bdc1e..04cd367bb 100644
--- a/app/templates/client/app/main/main(coffee).coffee
+++ b/app/templates/client/app/main/main(coffee).coffee
@@ -1,15 +1,15 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-<% if(filters.ngroute) { %>.config ($routeProvider) ->
+<% if (filters.ngroute) { %>.config ($routeProvider) ->
   $routeProvider
   .when '/',
     templateUrl: 'app/main/main.html'
     controller: 'MainCtrl'
-<% } %><% if(filters.uirouter) { %>.config ($stateProvider) ->
+<% } %><% if (filters.uirouter) { %>.config ($stateProvider) ->
   $stateProvider
   .state 'main',
     url: '/'
     templateUrl: 'app/main/main.html'
     controller: 'MainCtrl'
-<% } %>
\ No newline at end of file
+<% } %>
diff --git a/app/templates/client/app/main/main(css).css b/app/templates/client/app/main/main(css).css
index c396852d6..b49092ec1 100644
--- a/app/templates/client/app/main/main(css).css
+++ b/app/templates/client/app/main/main(css).css
@@ -1,34 +1,27 @@
 .thing-form {
-    margin: 20px 0;
+  margin: 20px 0;
 }
 
 #banner {
-    border-bottom: none;
-    margin-top: -20px;
+  border-bottom: none;
+  margin-top: -20px;
 }
 
 #banner h1 {
-    font-size: 60px;
-    line-height: 1;
-    letter-spacing: -1px;
+  font-size: 60px;
+  line-height: 1;
+  letter-spacing: -1px;
 }
 
 .hero-unit {
-    position: relative;
-    padding: 30px 15px;
-    color: #F5F5F5;
-    text-align: center;
-    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
-    background: #4393B9;
-}
-
-.footer {
-    text-align: center;
-    padding: 30px 0;
-    margin-top: 70px;
-    border-top: 1px solid #E5E5E5;
+  position: relative;
+  padding: 30px 15px;
+  color: #F5F5F5;
+  text-align: center;
+  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
+  background: #4393B9;
 }
 
 .navbar-text {
-    margin-left: 15px;
-}
\ No newline at end of file
+  margin-left: 15px;
+}
diff --git a/app/templates/client/app/main/main(html).html b/app/templates/client/app/main/main(html).html
index cd0f185b2..0d745d9a8 100644
--- a/app/templates/client/app/main/main(html).html
+++ b/app/templates/client/app/main/main(html).html
@@ -1,4 +1,4 @@
-<div ng-include="'components/navbar/navbar.html'"></div>
+<navbar></navbar>
 
 <header class="hero-unit" id="banner">
   <div class="container">
@@ -13,10 +13,10 @@ <h1>'Allo, 'Allo!</h1>
     <div class="col-lg-12">
       <h1 class="page-header">Features:</h1>
       <ul class="nav nav-tabs nav-stacked col-md-4 col-lg-4 col-sm-6" ng-repeat="thing in awesomeThings">
-        <li><a href="#" tooltip="{{thing.info}}">{{thing.name}}<% if(filters.socketio) { %><button type="button" class="close" ng-click="deleteThing(thing)">&times;</button><% } %></a></li>
+        <li><a href="#" tooltip="{{thing.info}}">{{thing.name}}<% if (filters.socketio) { %><button type="button" class="close" ng-click="deleteThing(thing)">&times;</button><% } %></a></li>
       </ul>
     </div>
-  </div><% if(filters.socketio) { %>
+  </div><% if (filters.socketio) { %>
 
   <form class="thing-form">
     <label>Syncs in realtime across clients</label>
@@ -29,10 +29,4 @@ <h1 class="page-header">Features:</h1>
   </form><% } %>
 </div>
 
-<footer class="footer">
-  <div class="container">
-      <p>Angular Fullstack v<%= pkg.version %> |
-        <a href="https://twitter.com/tyhenkel">@tyhenkel</a> |
-         <a href="https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open">Issues</a></p>
-  </div>
-</footer>
+<footer></footer>
diff --git a/app/templates/client/app/main/main(jade).jade b/app/templates/client/app/main/main(jade).jade
index 76784c855..3277e7b05 100644
--- a/app/templates/client/app/main/main(jade).jade
+++ b/app/templates/client/app/main/main(jade).jade
@@ -1,4 +1,4 @@
-div(ng-include='"components/navbar/navbar.html"')
+navbar
 
 header#banner.hero-unit
   .container
@@ -13,8 +13,8 @@ header#banner.hero-unit
       ul.nav.nav-tabs.nav-stacked.col-md-4.col-lg-4.col-sm-6(ng-repeat='thing in awesomeThings')
         li
           a(href='#', tooltip='{{thing.info}}')
-            | {{thing.name}}<% if(filters.socketio) { %>
-            button.close(type='button', ng-click='deleteThing(thing)') ×<% } %><% if(filters.socketio) { %>
+            | {{thing.name}}<% if (filters.socketio) { %>
+            button.close(type='button', ng-click='deleteThing(thing)') &times;<% } %><% if (filters.socketio) { %>
 
   form.thing-form
     label Syncs in realtime across clients
@@ -23,11 +23,4 @@ header#banner.hero-unit
       span.input-group-btn
         button.btn.btn-primary(type='submit', ng-click='addThing()') Add New<% } %>
 
-footer.footer
-  .container
-    p
-      | Angular Fullstack v<%= pkg.version %>
-      = ' | '
-      a(href='https://twitter.com/tyhenkel') @tyhenkel
-      = ' | '
-      a(href='https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open') Issues
\ No newline at end of file
+footer
diff --git a/app/templates/client/app/main/main(js).js b/app/templates/client/app/main/main(js).js
index 1d3bc318a..165181ffe 100644
--- a/app/templates/client/app/main/main(js).js
+++ b/app/templates/client/app/main/main(js).js
@@ -1,17 +1,17 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  <% if(filters.ngroute) { %>.config(function ($routeProvider) {
+  <% if (filters.ngroute) { %>.config(function($routeProvider) {
     $routeProvider
       .when('/', {
         templateUrl: 'app/main/main.html',
         controller: 'MainCtrl'
       });
-  });<% } %><% if(filters.uirouter) { %>.config(function ($stateProvider) {
+  });<% } %><% if (filters.uirouter) { %>.config(function($stateProvider) {
     $stateProvider
       .state('main', {
         url: '/',
         templateUrl: 'app/main/main.html',
         controller: 'MainCtrl'
       });
-  });<% } %>
\ No newline at end of file
+  });<% } %>
diff --git a/app/templates/client/app/main/main(less).less b/app/templates/client/app/main/main(less).less
index 39636ab2d..b49092ec1 100644
--- a/app/templates/client/app/main/main(less).less
+++ b/app/templates/client/app/main/main(less).less
@@ -3,32 +3,25 @@
 }
 
 #banner {
-    border-bottom: none;
-    margin-top: -20px;
+  border-bottom: none;
+  margin-top: -20px;
 }
 
 #banner h1 {
-    font-size: 60px;
-    line-height: 1;
-    letter-spacing: -1px;
+  font-size: 60px;
+  line-height: 1;
+  letter-spacing: -1px;
 }
 
 .hero-unit {
-    position: relative;
-    padding: 30px 15px;
-    color: #F5F5F5;
-    text-align: center;
-    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
-    background: #4393B9;
-}
-
-.footer {
-    text-align: center;
-    padding: 30px 0;
-    margin-top: 70px;
-    border-top: 1px solid #E5E5E5;
+  position: relative;
+  padding: 30px 15px;
+  color: #F5F5F5;
+  text-align: center;
+  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
+  background: #4393B9;
 }
 
 .navbar-text {
-    margin-left: 15px;
-}
\ No newline at end of file
+  margin-left: 15px;
+}
diff --git a/app/templates/client/app/main/main(sass).scss b/app/templates/client/app/main/main(sass).scss
index aa19c3649..b49092ec1 100644
--- a/app/templates/client/app/main/main(sass).scss
+++ b/app/templates/client/app/main/main(sass).scss
@@ -1,34 +1,27 @@
 .thing-form {
-    margin: 20px 0;
+  margin: 20px 0;
 }
 
 #banner {
-    border-bottom: none;
-    margin-top: -20px;
+  border-bottom: none;
+  margin-top: -20px;
 }
 
 #banner h1 {
-    font-size: 60px;
-    line-height: 1;
-    letter-spacing: -1px;
+  font-size: 60px;
+  line-height: 1;
+  letter-spacing: -1px;
 }
 
 .hero-unit {
-    position: relative;
-    padding: 30px 15px;
-    color: #F5F5F5;
-    text-align: center;
-    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
-    background: #4393B9;
-}
-
-.footer {
-    text-align: center;
-    padding: 30px 0;
-    margin-top: 70px;
-    border-top: 1px solid #E5E5E5;
+  position: relative;
+  padding: 30px 15px;
+  color: #F5F5F5;
+  text-align: center;
+  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
+  background: #4393B9;
 }
 
 .navbar-text {
   margin-left: 15px;
-}
\ No newline at end of file
+}
diff --git a/app/templates/client/app/main/main(stylus).styl b/app/templates/client/app/main/main(stylus).styl
index 9ba91c3a3..c3915218c 100644
--- a/app/templates/client/app/main/main(stylus).styl
+++ b/app/templates/client/app/main/main(stylus).styl
@@ -1,28 +1,22 @@
 .thing-form
-    margin 20px 0
+  margin 20px 0
 
 #banner
-    border-bottom none
-    margin-top -20px
+  border-bottom none
+  margin-top -20px
 
 #banner h1
-    font-size 60px
-    letter-spacing -1px
-    line-height 1
+  font-size 60px
+  letter-spacing -1px
+  line-height 1
 
 .hero-unit
-    background #4393B9
-    color #F5F5F5
-    padding 30px 15px
-    position relative
-    text-align center
-    text-shadow 0 1px 0 rgba(0, 0, 0, 0.1)
-
-.footer
-    border-top 1px solid #E5E5E5
-    margin-top 70px
-    padding 30px 0
-    text-align center
+  background #4393B9
+  color #F5F5F5
+  padding 30px 15px
+  position relative
+  text-align center
+  text-shadow 0 1px 0 rgba(0, 0, 0, 0.1)
 
 .navbar-text
-    margin-left 15px
\ No newline at end of file
+  margin-left 15px
diff --git a/app/templates/client/app/main/main.controller(coffee).coffee b/app/templates/client/app/main/main.controller(coffee).coffee
index 143e7f387..4b04a951b 100644
--- a/app/templates/client/app/main/main.controller(coffee).coffee
+++ b/app/templates/client/app/main/main.controller(coffee).coffee
@@ -1,13 +1,13 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-.controller 'MainCtrl', ($scope, $http<% if(filters.socketio) { %>, socket<% } %>) ->
+.controller 'MainCtrl', ($scope, $http<% if (filters.socketio) { %>, socket<% } %>) ->
   $scope.awesomeThings = []
 
   $http.get('/api/things').success (awesomeThings) ->
     $scope.awesomeThings = awesomeThings
-    <% if(filters.socketio) { %>socket.syncUpdates 'thing', $scope.awesomeThings<% } %>
-<% if(filters.mongoose) { %>
+    <% if (filters.socketio) { %>socket.syncUpdates 'thing', $scope.awesomeThings<% } %>
+<% if (filters.models) { %>
   $scope.addThing = ->
     return if $scope.newThing is ''
     $http.post '/api/things',
@@ -16,7 +16,7 @@ angular.module '<%= scriptAppName %>'
     $scope.newThing = ''
 
   $scope.deleteThing = (thing) ->
-    $http.delete '/api/things/' + thing._id<% } %><% if(filters.socketio) { %>
+    $http.delete '/api/things/' + thing._id<% } %><% if (filters.socketio) { %>
 
   $scope.$on '$destroy', ->
     socket.unsyncUpdates 'thing'<% } %>
diff --git a/app/templates/client/app/main/main.controller(js).js b/app/templates/client/app/main/main.controller(js).js
index 433a10fe4..345d9909d 100644
--- a/app/templates/client/app/main/main.controller(js).js
+++ b/app/templates/client/app/main/main.controller(js).js
@@ -1,16 +1,16 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .controller('MainCtrl', function ($scope, $http<% if(filters.socketio) { %>, socket<% } %>) {
+  .controller('MainCtrl', function($scope, $http<% if (filters.socketio) { %>, socket<% } %>) {
     $scope.awesomeThings = [];
 
     $http.get('/api/things').success(function(awesomeThings) {
-      $scope.awesomeThings = awesomeThings;<% if(filters.socketio) { %>
+      $scope.awesomeThings = awesomeThings;<% if (filters.socketio) { %>
       socket.syncUpdates('thing', $scope.awesomeThings);<% } %>
     });
-<% if(filters.mongoose) { %>
+<% if (filters.models) { %>
     $scope.addThing = function() {
-      if($scope.newThing === '') {
+      if ($scope.newThing === '') {
         return;
       }
       $http.post('/api/things', { name: $scope.newThing });
@@ -19,9 +19,9 @@ angular.module('<%= scriptAppName %>')
 
     $scope.deleteThing = function(thing) {
       $http.delete('/api/things/' + thing._id);
-    };<% } %><% if(filters.socketio) { %>
+    };<% } %><% if (filters.socketio) { %>
 
-    $scope.$on('$destroy', function () {
+    $scope.$on('$destroy', function() {
       socket.unsyncUpdates('thing');
     });<% } %>
   });
diff --git a/app/templates/client/app/main/main.controller.spec(coffee).coffee b/app/templates/client/app/main/main.controller.spec(coffee).coffee
index efe9b39a6..a72ae8695 100644
--- a/app/templates/client/app/main/main.controller.spec(coffee).coffee
+++ b/app/templates/client/app/main/main.controller.spec(coffee).coffee
@@ -3,15 +3,17 @@
 describe 'Controller: MainCtrl', ->
 
   # load the controller's module
-  beforeEach module '<%= scriptAppName %>' <% if(filters.socketio) {%>
+  beforeEach module '<%= scriptAppName %>' <% if (filters.uirouter) {%>
+  beforeEach module 'stateMock' <% } %><% if (filters.socketio) {%>
   beforeEach module 'socketMock' <% } %>
 
   MainCtrl = undefined
-  scope = undefined
+  scope = undefined<% if (filters.uirouter) {%>
+  state = undefined<% } %>
   $httpBackend = undefined
 
   # Initialize the controller and a mock scope
-  beforeEach inject (_$httpBackend_, $controller, $rootScope) ->
+  beforeEach inject (_$httpBackend_, $controller, $rootScope<% if (filters.uirouter) {%>, $state<% } %>) ->
     $httpBackend = _$httpBackend_
     $httpBackend.expectGET('/api/things').respond [
       'HTML5 Boilerplate'
@@ -19,10 +21,12 @@ describe 'Controller: MainCtrl', ->
       'Karma'
       'Express'
     ]
-    scope = $rootScope.$new()
+    scope = $rootScope.$new()<% if (filters.uirouter) {%>
+    state = $state<% } %>
     MainCtrl = $controller 'MainCtrl',
       $scope: scope
 
   it 'should attach a list of things to the scope', ->
-    $httpBackend.flush()
-    expect(scope.awesomeThings.length).toBe 4
\ No newline at end of file
+    $httpBackend.flush()<% if (filters.jasmine) { %>
+    expect(scope.awesomeThings.length).toBe 4 <% } if (filters.mocha) { %>
+    <%= does("scope.awesomeThings.length") %>.equal 4<% } %>
diff --git a/app/templates/client/app/main/main.controller.spec(js).js b/app/templates/client/app/main/main.controller.spec(js).js
index 373e9db08..71fd2a783 100644
--- a/app/templates/client/app/main/main.controller.spec(js).js
+++ b/app/templates/client/app/main/main.controller.spec(js).js
@@ -1,29 +1,33 @@
 'use strict';
 
-describe('Controller: MainCtrl', function () {
+describe('Controller: MainCtrl', function() {
 
   // load the controller's module
-  beforeEach(module('<%= scriptAppName %>'));<% if(filters.socketio) {%>
+  beforeEach(module('<%= scriptAppName %>'));<% if (filters.uirouter) {%>
+  beforeEach(module('stateMock'));<% } %><% if (filters.socketio) {%>
   beforeEach(module('socketMock'));<% } %>
 
-  var MainCtrl,
-      scope,
-      $httpBackend;
+  var MainCtrl;
+  var scope;<% if (filters.uirouter) {%>
+  var state;<% } %>
+  var $httpBackend;
 
   // Initialize the controller and a mock scope
-  beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) {
+  beforeEach(inject(function(_$httpBackend_, $controller, $rootScope<% if (filters.uirouter) {%>, $state<% } %>) {
     $httpBackend = _$httpBackend_;
     $httpBackend.expectGET('/api/things')
       .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']);
 
-    scope = $rootScope.$new();
+    scope = $rootScope.$new();<% if (filters.uirouter) {%>
+    state = $state;<% } %>
     MainCtrl = $controller('MainCtrl', {
       $scope: scope
     });
   }));
 
-  it('should attach a list of things to the scope', function () {
-    $httpBackend.flush();
-    expect(scope.awesomeThings.length).toBe(4);
+  it('should attach a list of things to the scope', function() {
+    $httpBackend.flush();<% if (filters.jasmine) { %>
+    expect(scope.awesomeThings.length).toBe(4);<% } if (filters.mocha) { %>
+    <%= does("scope.awesomeThings.length") %>.equal(4);<% } %>
   });
 });
diff --git a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee
index ac503ed0b..2892d04ed 100644
--- a/app/templates/client/components/auth(auth)/auth.service(coffee).coffee
+++ b/app/templates/client/components/auth(auth)/auth.service(coffee).coffee
@@ -1,43 +1,41 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-.factory 'Auth', ($location, $rootScope, $http, User, $cookieStore, $q) ->
-  currentUser = if $cookieStore.get 'token' then User.get() else {}
+.factory 'Auth', ($http, User, $cookies, $q) ->
+  currentUser = if $cookies.get 'token' then User.get() else {}
 
   ###
   Authenticate user and save token
 
   @param  {Object}   user     - login info
-  @param  {Function} callback - optional
+  @param  {Function} callback - optional, function(error, user)
   @return {Promise}
   ###
   login: (user, callback) ->
-    deferred = $q.defer()
     $http.post '/auth/local',
       email: user.email
       password: user.password
 
-    .success (data) ->
-      $cookieStore.put 'token', data.token
+    .then (res) ->
+      $cookies.put 'token', res.data.token
       currentUser = User.get()
-      deferred.resolve data
-      callback?()
+      currentUser.$promise
 
-    .error (err) =>
-      @logout()
-      deferred.reject err
-      callback? err
+    .then (user) ->
+      callback? null, user
+      user
 
-    deferred.promise
+    .catch (err) =>
+      @logout()
+      callback? err.data
+      $q.reject err.data
 
 
   ###
   Delete access token and user info
-
-  @param  {Function}
   ###
   logout: ->
-    $cookieStore.remove 'token'
+    $cookies.remove 'token'
     currentUser = {}
     return
 
@@ -46,15 +44,15 @@ angular.module '<%= scriptAppName %>'
   Create a new user
 
   @param  {Object}   user     - user info
-  @param  {Function} callback - optional
+  @param  {Function} callback - optional, function(error, user)
   @return {Promise}
   ###
   createUser: (user, callback) ->
     User.save user,
       (data) ->
-        $cookieStore.put 'token', data.token
+        $cookies.put 'token', data.token
         currentUser = User.get()
-        callback? user
+        callback? null, user
 
       , (err) =>
         @logout()
@@ -68,7 +66,7 @@ angular.module '<%= scriptAppName %>'
 
   @param  {String}   oldPassword
   @param  {String}   newPassword
-  @param  {Function} callback    - optional
+  @param  {Function} callback    - optional, function(error, user)
   @return {Promise}
   ###
   changePassword: (oldPassword, newPassword, callback) ->
@@ -78,8 +76,8 @@ angular.module '<%= scriptAppName %>'
       oldPassword: oldPassword
       newPassword: newPassword
 
-    , (user) ->
-      callback? user
+    , () ->
+      callback? null
 
     , (err) ->
       callback? err
@@ -88,49 +86,65 @@ angular.module '<%= scriptAppName %>'
 
 
   ###
-  Gets all available info on authenticated user
+  Gets all available info on a user
+    (synchronous|asynchronous)
 
-  @return {Object} user
+  @param  {Function|*} callback - optional, funciton(user)
+  @return {Object|Promise}
   ###
-  getCurrentUser: ->
-    currentUser
+  getCurrentUser: (callback) ->
+    return currentUser  if arguments.length is 0
 
+    value = if (currentUser.hasOwnProperty("$promise")) then currentUser.$promise else currentUser
+    $q.when value
 
-  ###
-  Check if a user is logged in synchronously
+    .then (user) ->
+      callback? user
+      user
 
-  @return {Boolean}
-  ###
-  isLoggedIn: ->
-    currentUser.hasOwnProperty 'role'
+    , ->
+      callback? {}
+      {}
 
 
   ###
-  Waits for currentUser to resolve before checking if user is logged in
+  Check if a user is logged in
+    (synchronous|asynchronous)
+
+  @param  {Function|*} callback - optional, function(is)
+  @return {Bool|Promise}
   ###
-  isLoggedInAsync: (callback) ->
-    if currentUser.hasOwnProperty '$promise'
-      currentUser.$promise.then ->
-        callback? true
-        return
-      .catch ->
-        callback? false
-        return
+  isLoggedIn: (callback) ->
+    return currentUser.hasOwnProperty("role")  if arguments.length is 0
+
+    @getCurrentUser null
+
+    .then (user) ->
+      is_ = user.hasOwnProperty("role")
+      callback? is_
+      is_
 
-    else
-      callback? currentUser.hasOwnProperty 'role'
 
   ###
   Check if a user is an admin
+    (synchronous|asynchronous)
 
-  @return {Boolean}
+  @param  {Function|*} callback - optional, function(is)
+  @return {Bool|Promise}
   ###
-  isAdmin: ->
-    currentUser.role is 'admin'
+  isAdmin: (callback) ->
+    return currentUser.role is "admin"  if arguments.length is 0
+
+    @getCurrentUser null
+
+    .then (user) ->
+      is_ = user.role is "admin"
+      callback? is_
+      is_
 
 
   ###
   Get auth token
   ###
   getToken: ->
-    $cookieStore.get 'token'
+    $cookies.get 'token'
diff --git a/app/templates/client/components/auth(auth)/auth.service(js).js b/app/templates/client/components/auth(auth)/auth.service(js).js
index 9afb12da9..2a1fcb480 100644
--- a/app/templates/client/components/auth(auth)/auth.service(js).js
+++ b/app/templates/client/components/auth(auth)/auth.service(js).js
@@ -1,9 +1,20 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) {
-    var currentUser = {};
-    if($cookieStore.get('token')) {
+  .factory('Auth', function Auth($http, User, $cookies, $q) {
+    /**
+     * Return a callback or noop function
+     *
+     * @param  {Function|*} cb - a 'potential' function
+     * @return {Function}
+     */
+    var safeCb = function(cb) {
+      return (angular.isFunction(cb)) ? cb : angular.noop;
+    },
+
+    currentUser = {};
+
+    if ($cookies.get('token')) {
       currentUser = User.get();
     }
 
@@ -13,39 +24,35 @@ angular.module('<%= scriptAppName %>')
        * Authenticate user and save token
        *
        * @param  {Object}   user     - login info
-       * @param  {Function} callback - optional
+       * @param  {Function} callback - optional, function(error, user)
        * @return {Promise}
        */
       login: function(user, callback) {
-        var cb = callback || angular.noop;
-        var deferred = $q.defer();
-
-        $http.post('/auth/local', {
+        return $http.post('/auth/local', {
           email: user.email,
           password: user.password
-        }).
-        success(function(data) {
-          $cookieStore.put('token', data.token);
+        })
+        .then(function(res) {
+          $cookies.put('token', res.data.token);
           currentUser = User.get();
-          deferred.resolve(data);
-          return cb();
-        }).
-        error(function(err) {
+          return currentUser.$promise;
+        })
+        .then(function(user) {
+          safeCb(callback)(null, user);
+          return user;
+        })
+        .catch(function(err) {
           this.logout();
-          deferred.reject(err);
-          return cb(err);
+          safeCb(callback)(err.data);
+          return $q.reject(err.data);
         }.bind(this));
-
-        return deferred.promise;
       },
 
       /**
        * Delete access token and user info
-       *
-       * @param  {Function}
        */
       logout: function() {
-        $cookieStore.remove('token');
+        $cookies.remove('token');
         currentUser = {};
       },
 
@@ -53,21 +60,19 @@ angular.module('<%= scriptAppName %>')
        * Create a new user
        *
        * @param  {Object}   user     - user info
-       * @param  {Function} callback - optional
+       * @param  {Function} callback - optional, function(error, user)
        * @return {Promise}
        */
       createUser: function(user, callback) {
-        var cb = callback || angular.noop;
-
         return User.save(user,
           function(data) {
-            $cookieStore.put('token', data.token);
+            $cookies.put('token', data.token);
             currentUser = User.get();
-            return cb(user);
+            return safeCb(callback)(null, user);
           },
           function(err) {
             this.logout();
-            return cb(err);
+            return safeCb(callback)(err);
           }.bind(this)).$promise;
       },
 
@@ -76,71 +81,90 @@ angular.module('<%= scriptAppName %>')
        *
        * @param  {String}   oldPassword
        * @param  {String}   newPassword
-       * @param  {Function} callback    - optional
+       * @param  {Function} callback    - optional, function(error, user)
        * @return {Promise}
        */
       changePassword: function(oldPassword, newPassword, callback) {
-        var cb = callback || angular.noop;
-
         return User.changePassword({ id: currentUser._id }, {
           oldPassword: oldPassword,
           newPassword: newPassword
-        }, function(user) {
-          return cb(user);
+        }, function() {
+          return safeCb(callback)(null);
         }, function(err) {
-          return cb(err);
+          return safeCb(callback)(err);
         }).$promise;
       },
 
       /**
-       * Gets all available info on authenticated user
+       * Gets all available info on a user
+       *   (synchronous|asynchronous)
        *
-       * @return {Object} user
+       * @param  {Function|*} callback - optional, funciton(user)
+       * @return {Object|Promise}
        */
-      getCurrentUser: function() {
-        return currentUser;
+      getCurrentUser: function(callback) {
+        if (arguments.length === 0) {
+          return currentUser;
+        }
+
+        var value = (currentUser.hasOwnProperty('$promise')) ? currentUser.$promise : currentUser;
+        return $q.when(value)
+          .then(function(user) {
+            safeCb(callback)(user);
+            return user;
+          }, function() {
+            safeCb(callback)({});
+            return {};
+          });
       },
 
       /**
        * Check if a user is logged in
+       *   (synchronous|asynchronous)
        *
-       * @return {Boolean}
+       * @param  {Function|*} callback - optional, function(is)
+       * @return {Bool|Promise}
        */
-      isLoggedIn: function() {
-        return currentUser.hasOwnProperty('role');
-      },
+      isLoggedIn: function(callback) {
+        if (arguments.length === 0) {
+          return currentUser.hasOwnProperty('role');
+        }
 
-      /**
-       * Waits for currentUser to resolve before checking if user is logged in
-       */
-      isLoggedInAsync: function(cb) {
-        if(currentUser.hasOwnProperty('$promise')) {
-          currentUser.$promise.then(function() {
-            cb(true);
-          }).catch(function() {
-            cb(false);
+        return this.getCurrentUser(null)
+          .then(function(user) {
+            var is = user.hasOwnProperty('role');
+            safeCb(callback)(is);
+            return is;
           });
-        } else if(currentUser.hasOwnProperty('role')) {
-          cb(true);
-        } else {
-          cb(false);
-        }
       },
 
-      /**
-       * Check if a user is an admin
-       *
-       * @return {Boolean}
-       */
-      isAdmin: function() {
-        return currentUser.role === 'admin';
+       /**
+        * Check if a user is an admin
+        *   (synchronous|asynchronous)
+        *
+        * @param  {Function|*} callback - optional, function(is)
+        * @return {Bool|Promise}
+        */
+      isAdmin: function(callback) {
+        if (arguments.length === 0) {
+          return currentUser.role === 'admin';
+        }
+
+        return this.getCurrentUser(null)
+          .then(function(user) {
+            var is = user.role === 'admin';
+            safeCb(callback)(is);
+            return is;
+          });
       },
 
       /**
        * Get auth token
+       *
+       * @return {String} - a token string used for authenticating
        */
       getToken: function() {
-        return $cookieStore.get('token');
+        return $cookies.get('token');
       }
     };
   });
diff --git a/app/templates/client/components/footer/footer(css).css b/app/templates/client/components/footer/footer(css).css
new file mode 100644
index 000000000..cd1753eed
--- /dev/null
+++ b/app/templates/client/components/footer/footer(css).css
@@ -0,0 +1,6 @@
+footer.footer {
+  text-align: center;
+  padding: 30px 0;
+  margin-top: 70px;
+  border-top: 1px solid #E5E5E5;
+}
diff --git a/app/templates/client/components/footer/footer(html).html b/app/templates/client/components/footer/footer(html).html
new file mode 100644
index 000000000..3f9f7ffb9
--- /dev/null
+++ b/app/templates/client/components/footer/footer(html).html
@@ -0,0 +1,6 @@
+<div class="container">
+  <p>Angular Fullstack v<%= pkg.version %> |
+    <a href="https://twitter.com/tyhenkel">@tyhenkel</a> |
+    <a href="https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open">Issues</a>
+  </p>
+</div>
diff --git a/app/templates/client/components/footer/footer(jade).jade b/app/templates/client/components/footer/footer(jade).jade
new file mode 100644
index 000000000..a0bd84a1d
--- /dev/null
+++ b/app/templates/client/components/footer/footer(jade).jade
@@ -0,0 +1,7 @@
+.container
+  p
+    | Angular Fullstack v<%= pkg.version %>
+    = ' | '
+    a(href='https://twitter.com/tyhenkel') @tyhenkel
+    = ' | '
+    a(href='https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open') Issues
diff --git a/app/templates/client/components/footer/footer(less).less b/app/templates/client/components/footer/footer(less).less
new file mode 100644
index 000000000..cd1753eed
--- /dev/null
+++ b/app/templates/client/components/footer/footer(less).less
@@ -0,0 +1,6 @@
+footer.footer {
+  text-align: center;
+  padding: 30px 0;
+  margin-top: 70px;
+  border-top: 1px solid #E5E5E5;
+}
diff --git a/app/templates/client/components/footer/footer(sass).scss b/app/templates/client/components/footer/footer(sass).scss
new file mode 100644
index 000000000..cd1753eed
--- /dev/null
+++ b/app/templates/client/components/footer/footer(sass).scss
@@ -0,0 +1,6 @@
+footer.footer {
+  text-align: center;
+  padding: 30px 0;
+  margin-top: 70px;
+  border-top: 1px solid #E5E5E5;
+}
diff --git a/app/templates/client/components/footer/footer(stylus).styl b/app/templates/client/components/footer/footer(stylus).styl
new file mode 100644
index 000000000..ad725136b
--- /dev/null
+++ b/app/templates/client/components/footer/footer(stylus).styl
@@ -0,0 +1,5 @@
+footer.footer
+  border-top 1px solid #E5E5E5
+  margin-top 70px
+  padding 30px 0
+  text-align center
diff --git a/app/templates/client/components/footer/footer.directive(coffee).coffee b/app/templates/client/components/footer/footer.directive(coffee).coffee
new file mode 100644
index 000000000..467006759
--- /dev/null
+++ b/app/templates/client/components/footer/footer.directive(coffee).coffee
@@ -0,0 +1,8 @@
+'use strict'
+
+angular.module '<%= scriptAppName %>'
+.directive 'footer', ->
+  templateUrl: 'components/footer/footer.html'
+  restrict: 'E',
+  link: (scope, element) ->
+    element.addClass('footer')
diff --git a/app/templates/client/components/footer/footer.directive(js).js b/app/templates/client/components/footer/footer.directive(js).js
new file mode 100644
index 000000000..a640e2289
--- /dev/null
+++ b/app/templates/client/components/footer/footer.directive(js).js
@@ -0,0 +1,12 @@
+'use strict';
+
+angular.module('<%= scriptAppName %>')
+  .directive('footer', function () {
+    return {
+      templateUrl: 'components/footer/footer.html',
+      restrict: 'E',
+      link: function (scope, element) {
+        element.addClass('footer');
+      }
+    };
+  });
diff --git a/app/templates/client/components/modal(uibootstrap)/modal(css).css b/app/templates/client/components/modal(uibootstrap)/modal(css).css
index f5cc0d9e7..ae0406856 100644
--- a/app/templates/client/components/modal(uibootstrap)/modal(css).css
+++ b/app/templates/client/components/modal(uibootstrap)/modal(css).css
@@ -20,4 +20,4 @@
 }
 .modal-danger .modal-header {
   background: #d9534f;
-}
\ No newline at end of file
+}
diff --git a/app/templates/client/components/modal(uibootstrap)/modal(html).html b/app/templates/client/components/modal(uibootstrap)/modal(html).html
index 4580254ff..f04d0db03 100644
--- a/app/templates/client/components/modal(uibootstrap)/modal(html).html
+++ b/app/templates/client/components/modal(uibootstrap)/modal(html).html
@@ -8,4 +8,4 @@ <h4 ng-if="modal.title" ng-bind="modal.title" class="modal-title"></h4>
 </div>
 <div class="modal-footer">
   <button ng-repeat="button in modal.buttons" ng-class="button.classes" ng-click="button.click($event)" ng-bind="button.text" class="btn"></button>
-</div>
\ No newline at end of file
+</div>
diff --git a/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(coffee).coffee b/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(coffee).coffee
index d255f614d..cf0e1ccf0 100644
--- a/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(coffee).coffee
+++ b/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(coffee).coffee
@@ -9,4 +9,4 @@ angular.module '<%= scriptAppName %>'
   require: 'ngModel'
   link: (scope, element, attrs, ngModel) ->
     element.on 'keydown', ->
-      ngModel.$setValidity 'mongoose', true
\ No newline at end of file
+      ngModel.$setValidity 'mongoose', true
diff --git a/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(js).js b/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(js).js
index 8a331009b..a71cb03cf 100644
--- a/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(js).js
+++ b/app/templates/client/components/mongoose-error(auth)/mongoose-error.directive(js).js
@@ -14,4 +14,4 @@ angular.module('<%= scriptAppName %>')
         });
       }
     };
-  });
\ No newline at end of file
+  });
diff --git a/app/templates/client/components/navbar/navbar(html).html b/app/templates/client/components/navbar/navbar(html).html
index 71f8606dd..ec9e4682d 100644
--- a/app/templates/client/components/navbar/navbar(html).html
+++ b/app/templates/client/components/navbar/navbar(html).html
@@ -11,18 +11,18 @@
     </div>
     <div collapse="isCollapsed" class="navbar-collapse collapse" id="navbar-main">
       <ul class="nav navbar-nav">
-        <li ng-repeat="item in menu" ng-class="{active: isActive(item.link)}">
-            <a ng-href="{{item.link}}">{{item.title}}</a>
-        </li><% if(filters.auth) { %>
-        <li ng-show="isAdmin()" ng-class="{active: isActive('/admin')}"><a href="/admin">Admin</a></li><% } %>
-      </ul><% if(filters.auth) { %>
+        <li ng-repeat="item in menu" <% if (filters.uirouter) { %>ui-sref-active="active"<% } else { %>ng-class="{active: isActive(item.link)}"<% } %>>
+            <a <% if (filters.uirouter) { %>ui-sref="{{item.state}}"<% } else { %>ng-href="{{item.link}}"<% } %>>{{item.title}}</a>
+        </li><% if (filters.auth) { %>
+        <li ng-show="isAdmin()" <% if (filters.uirouter) { %>ui-sref-active="active"<% } else { %>ng-class="{active: isActive('/admin')}"<% } %>><a <% if (filters.uirouter) { %>ui-sref="admin"<% } else { %>href="/admin"<% } %>>Admin</a></li><% } %>
+      </ul><% if (filters.auth) { %>
 
       <ul class="nav navbar-nav navbar-right">
-        <li ng-hide="isLoggedIn()" ng-class="{active: isActive('/signup')}"><a href="/signup">Sign up</a></li>
-        <li ng-hide="isLoggedIn()" ng-class="{active: isActive('/login')}"><a href="/login">Login</a></li>
+        <li ng-hide="isLoggedIn()" <% if (filters.uirouter) { %>ui-sref-active="active"<% } else { %>ng-class="{active: isActive('/signup')}"<% } %>><a <% if (filters.uirouter) { %>ui-sref="signup"<% } else { %>href="/signup"<% } %>>Sign up</a></li>
+        <li ng-hide="isLoggedIn()" <% if (filters.uirouter) { %>ui-sref-active="active"<% } else { %>ng-class="{active: isActive('/login')}"<% } %>><a <% if (filters.uirouter) { %>ui-sref="login"<% } else { %>href="/login"<% } %>>Login</a></li>
         <li ng-show="isLoggedIn()"><p class="navbar-text">Hello {{ getCurrentUser().name }}</p> </li>
-        <li ng-show="isLoggedIn()" ng-class="{active: isActive('/settings')}"><a href="/settings"><span class="glyphicon glyphicon-cog"></span></a></li>
-        <li ng-show="isLoggedIn()" ng-class="{active: isActive('/logout')}"><a href="" ng-click="logout()">Logout</a></li>
+        <li ng-show="isLoggedIn()" <% if (filters.uirouter) { %>ui-sref-active="active"<% } else { %>ng-class="{active: isActive('/settings')}"<% } %>><a <% if (filters.uirouter) { %>ui-sref="settings"<% } else { %>href="/settings"<% } %>><span class="glyphicon glyphicon-cog"></span></a></li>
+        <li ng-show="isLoggedIn()"><a <% if (filters.uirouter) { %>ui-sref="logout"<% } else { %>href="/logout"<% } %>>Logout</a></li>
       </ul><% } %>
     </div>
   </div>
diff --git a/app/templates/client/components/navbar/navbar(jade).jade b/app/templates/client/components/navbar/navbar(jade).jade
index 2b17f29c3..e20a8fffa 100644
--- a/app/templates/client/components/navbar/navbar(jade).jade
+++ b/app/templates/client/components/navbar/navbar(jade).jade
@@ -10,25 +10,25 @@ div.navbar.navbar-default.navbar-static-top(ng-controller='NavbarCtrl')
 
     div#navbar-main.navbar-collapse.collapse(collapse='isCollapsed')
       ul.nav.navbar-nav
-        li(ng-repeat='item in menu', ng-class='{active: isActive(item.link)}')
-          a(ng-href='{{item.link}}') {{item.title}}<% if(filters.auth) { %>
+        li(ng-repeat='item in menu', <% if (filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive(item.link)}'<% } %>)
+          a(<% if (filters.uirouter) { %>ui-sref='{{item.state}}'<% } else { %>ng-href='{{item.link}}'<% } %>) {{item.title}}<% if (filters.auth) { %>
 
-        li(ng-show='isAdmin()', ng-class='{active: isActive("/admin")}')
-          a(href='/admin') Admin<% } %><% if(filters.auth) { %>
+        li(ng-show='isAdmin()', <% if (filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/admin")}'<% } %>)
+          a(<% if (filters.uirouter) { %>ui-sref='admin'<% } else { %>href='/admin'<% } %>) Admin
 
       ul.nav.navbar-nav.navbar-right
-        li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/signup")}')
-          a(href='/signup') Sign up
+        li(ng-hide='isLoggedIn()', <% if (filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/signup")}'<% } %>)
+          a(<% if (filters.uirouter) { %>ui-sref='signup'<% } else { %>href='/signup'<% } %>) Sign up
 
-        li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/login")}')
-          a(href='/login') Login
+        li(ng-hide='isLoggedIn()', <% if (filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/login")}'<% } %>)
+          a(<% if (filters.uirouter) { %>ui-sref='login'<% } else { %>href='/login'<% } %>) Login
 
         li(ng-show='isLoggedIn()')
           p.navbar-text Hello {{ getCurrentUser().name }}
 
-        li(ng-show='isLoggedIn()', ng-class='{active: isActive("/settings")}')
-          a(href='/settings')
+        li(ng-show='isLoggedIn()', <% if (filters.uirouter) { %>ui-sref-active='active'<% } else { %>ng-class='{active: isActive("/settings")}'<% } %>)
+          a(<% if (filters.uirouter) { %>ui-sref='settings'<% } else { %>href='/settings'<% } %>)
             span.glyphicon.glyphicon-cog
 
-        li(ng-show='isLoggedIn()', ng-class='{active: isActive("/logout")}')
-          a(href='', ng-click='logout()') Logout<% } %>
\ No newline at end of file
+        li(ng-show='isLoggedIn()')
+          a(<% if (filters.uirouter) { %>ui-sref='logout'<% } else { %>href='/logout'<% } %>) Logout<% } %>
diff --git a/app/templates/client/components/navbar/navbar.controller(coffee).coffee b/app/templates/client/components/navbar/navbar.controller(coffee).coffee
index d3804c5eb..98eaf2213 100644
--- a/app/templates/client/components/navbar/navbar.controller(coffee).coffee
+++ b/app/templates/client/components/navbar/navbar.controller(coffee).coffee
@@ -1,19 +1,15 @@
 'use strict'
 
 angular.module '<%= scriptAppName %>'
-.controller 'NavbarCtrl', ($scope, $location<% if(filters.auth) {%>, Auth<% } %>) ->
+.controller 'NavbarCtrl', ($scope<% if(!filters.uirouter) { %>, $location<% } %><% if (filters.auth) {%>, Auth<% } %>) ->
   $scope.menu = [
     title: 'Home'
-    link: '/'
+    <% if (filters.uirouter) { %>state: 'main'<% } else { %>link: '/'<% } %>
   ]
-  $scope.isCollapsed = true<% if(filters.auth) {%>
+  $scope.isCollapsed = true<% if (filters.auth) {%>
   $scope.isLoggedIn = Auth.isLoggedIn
   $scope.isAdmin = Auth.isAdmin
-  $scope.getCurrentUser = Auth.getCurrentUser
-
-  $scope.logout = ->
-    Auth.logout()
-    $location.path '/login'<% } %>
+  $scope.getCurrentUser = Auth.getCurrentUser<% } %><% if(!filters.uirouter) { %>
 
   $scope.isActive = (route) ->
-    route is $location.path()
\ No newline at end of file
+    route is $location.path()<% } %>
diff --git a/app/templates/client/components/navbar/navbar.controller(js).js b/app/templates/client/components/navbar/navbar.controller(js).js
index 4ce9dbcb5..b3eef7cf6 100644
--- a/app/templates/client/components/navbar/navbar.controller(js).js
+++ b/app/templates/client/components/navbar/navbar.controller(js).js
@@ -1,23 +1,18 @@
 'use strict';
 
 angular.module('<%= scriptAppName %>')
-  .controller('NavbarCtrl', function ($scope, $location<% if(filters.auth) {%>, Auth<% } %>) {
+  .controller('NavbarCtrl', function ($scope<% if(!filters.uirouter) { %>, $location<% } %><% if (filters.auth) {%>, Auth<% } %>) {
     $scope.menu = [{
       'title': 'Home',
-      'link': '/'
+      <% if (filters.uirouter) { %>'state': 'main'<% } else { %>'link': '/'<% } %>
     }];
 
-    $scope.isCollapsed = true;<% if(filters.auth) {%>
+    $scope.isCollapsed = true;<% if (filters.auth) {%>
     $scope.isLoggedIn = Auth.isLoggedIn;
     $scope.isAdmin = Auth.isAdmin;
-    $scope.getCurrentUser = Auth.getCurrentUser;
-
-    $scope.logout = function() {
-      Auth.logout();
-      $location.path('/login');
-    };<% } %>
+    $scope.getCurrentUser = Auth.getCurrentUser;<% } %><% if(!filters.uirouter) { %>
 
     $scope.isActive = function(route) {
       return route === $location.path();
-    };
-  });
\ No newline at end of file
+    };<% } %>
+  });
diff --git a/app/templates/client/components/navbar/navbar.directive(coffee).coffee b/app/templates/client/components/navbar/navbar.directive(coffee).coffee
new file mode 100644
index 000000000..bea476822
--- /dev/null
+++ b/app/templates/client/components/navbar/navbar.directive(coffee).coffee
@@ -0,0 +1,7 @@
+'use strict'
+
+angular.module '<%= scriptAppName %>'
+.directive 'navbar', ->
+  templateUrl: 'components/navbar/navbar.html'
+  restrict: 'E'
+  controller: 'NavbarCtrl'
diff --git a/app/templates/client/components/navbar/navbar.directive(js).js b/app/templates/client/components/navbar/navbar.directive(js).js
new file mode 100644
index 000000000..9153e6489
--- /dev/null
+++ b/app/templates/client/components/navbar/navbar.directive(js).js
@@ -0,0 +1,10 @@
+'use strict';
+
+angular.module('<%= scriptAppName %>')
+  .directive('navbar', function () {
+    return {
+      templateUrl: 'components/navbar/navbar.html',
+      restrict: 'E',
+      controller: 'NavbarCtrl'
+    };
+  });
diff --git a/app/templates/client/components/socket(socketio)/socket.mock.js b/app/templates/client/components/socket(socketio)/socket.mock(js).js
similarity index 98%
rename from app/templates/client/components/socket(socketio)/socket.mock.js
rename to app/templates/client/components/socket(socketio)/socket.mock(js).js
index 84a2e0c36..ba09c1d35 100644
--- a/app/templates/client/components/socket(socketio)/socket.mock.js
+++ b/app/templates/client/components/socket(socketio)/socket.mock(js).js
@@ -13,4 +13,4 @@ angular.module('socketMock', [])
       syncUpdates: function() {},
       unsyncUpdates: function() {}
     };
-  });
\ No newline at end of file
+  });
diff --git a/app/templates/client/components/socket(socketio)/socket.service.js b/app/templates/client/components/socket(socketio)/socket.service(js).js
similarity index 100%
rename from app/templates/client/components/socket(socketio)/socket.service.js
rename to app/templates/client/components/socket(socketio)/socket.service(js).js
diff --git a/app/templates/client/components/ui-router(uirouter)/ui-router.mock(coffee).coffee b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(coffee).coffee
new file mode 100644
index 000000000..ff3937c35
--- /dev/null
+++ b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(coffee).coffee
@@ -0,0 +1,26 @@
+'use strict'
+
+angular.module 'stateMock', []
+angular.module('stateMock').service '$state', ($q) ->
+  @expectedTransitions = []
+
+  @transitionTo = (stateName) ->
+    if @expectedTransitions.length > 0
+      expectedState = @expectedTransitions.shift()
+      throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName)  if expectedState isnt stateName
+    else
+      throw Error('No more transitions were expected! Tried to transition to ' + stateName)
+    console.log 'Mock transition to: ' + stateName
+    deferred = $q.defer()
+    promise = deferred.promise
+    deferred.resolve()
+    promise
+
+  @go = @transitionTo
+
+  @expectTransitionTo = (stateName) ->
+    @expectedTransitions.push stateName
+
+  @ensureAllTransitionsHappened = ->
+    throw Error('Not all transitions happened!')  if @expectedTransitions.length > 0
+  @
diff --git a/app/templates/client/components/ui-router(uirouter)/ui-router.mock(js).js b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(js).js
new file mode 100644
index 000000000..a5a1bf413
--- /dev/null
+++ b/app/templates/client/components/ui-router(uirouter)/ui-router.mock(js).js
@@ -0,0 +1,34 @@
+'use strict';
+
+angular.module('stateMock', []);
+angular.module('stateMock').service('$state', function($q) {
+    this.expectedTransitions = [];
+
+    this.transitionTo = function(stateName) {
+        if (this.expectedTransitions.length > 0) {
+            var expectedState = this.expectedTransitions.shift();
+            if (expectedState !== stateName) {
+                throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName);
+            }
+        } else {
+            throw Error('No more transitions were expected! Tried to transition to ' + stateName);
+        }
+        console.log('Mock transition to: ' + stateName);
+        var deferred = $q.defer();
+        var promise = deferred.promise;
+        deferred.resolve();
+        return promise;
+    };
+
+    this.go = this.transitionTo;
+
+    this.expectTransitionTo = function(stateName) {
+        this.expectedTransitions.push(stateName);
+    };
+
+    this.ensureAllTransitionsHappened = function() {
+        if (this.expectedTransitions.length > 0) {
+            throw Error('Not all transitions happened!');
+        }
+    };
+});
diff --git a/app/templates/client/index.html b/app/templates/client/index.html
index e9dcd5729..1c8d00d8e 100644
--- a/app/templates/client/index.html
+++ b/app/templates/client/index.html
@@ -5,7 +5,7 @@
 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
   <head>
     <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
     <base href="/">
     <title></title>
     <meta name="description" content="">
@@ -16,25 +16,25 @@
       <!-- endbower -->
     <!-- endbuild -->
     <!-- build:css({.tmp,client}) app/app.css -->
-    <link rel="stylesheet" href="app/app.css">
+      <link rel="stylesheet" href="app/app.css">
       <!-- injector:css -->
       <!-- endinjector -->
     <!-- endbuild -->
   </head>
   <body ng-app="<%= scriptAppName %>">
     <!--[if lt IE 7]>
-      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
+      <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
     <![endif]-->
 
     <!-- Add your site or application content here -->
-    <% if(filters.ngroute) { %><div ng-view=""></div><% } %><% if(filters.uirouter) { %><div ui-view=""></div><% } %>
+    <% if (filters.ngroute) { %><div ng-view=""></div><% } %><% if (filters.uirouter) { %><div ui-view=""></div><% } %>
 
     <!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
     <script>
       (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
       (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
       m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+      })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
 
       ga('create', 'UA-XXXXX-X');
       ga('send', 'pageview');
@@ -46,18 +46,14 @@
     <![endif]-->
     <!-- build:js({client,node_modules}) app/vendor.js -->
       <!-- bower:js -->
-      <!-- endbower --><% if(filters.socketio) { %>
+      <!-- endbower --><% if (filters.socketio) { %>
       <script src="socket.io-client/socket.io.js"></script><% } %>
     <!-- endbuild -->
-
-        <% if(filters.babel) { %>
-        <!-- build:js(.tmp) app/app.js -->
-        <% } else { %>
-        <!-- build:js({.tmp,client}) app/app.js -->
-        <% } %>
-        <script src="app/app.js"></script>
-          <!-- injector:js -->
-          <!-- endinjector -->
-        <!-- endbuild -->
-</body>
+    <!-- build:js(<% if(filters.babel) { %>.tmp<% }
+    else { %>{.tmp,client}<% } %>) app/app.js -->
+      <script src="app/app.js"></script>
+      <!-- injector:js -->
+      <!-- endinjector -->
+    <!-- endbuild -->
+  </body>
 </html>
diff --git a/app/templates/e2e/account(auth)/login/login.po.js b/app/templates/e2e/account(auth)/login/login.po.js
new file mode 100644
index 000000000..1186cdb6b
--- /dev/null
+++ b/app/templates/e2e/account(auth)/login/login.po.js
@@ -0,0 +1,27 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var LoginPage = function() {
+  this.form = element(by.css('.form'));
+  this.form.email = this.form.element(by.model('user.email'));
+  this.form.password = this.form.element(by.model('user.password'));
+  this.form.submit = this.form.element(by.css('.btn-login'));
+
+  this.login = function(data) {
+    for (var prop in data) {
+      var formElem = this.form[prop];
+      if (data.hasOwnProperty(prop) && formElem && typeof formElem.sendKeys === 'function') {
+        formElem.sendKeys(data[prop]);
+      }
+    }
+
+    this.form.submit.click();
+  };
+};
+
+module.exports = new LoginPage();
+
diff --git a/app/templates/e2e/account(auth)/login/login.spec(jasmine).js b/app/templates/e2e/account(auth)/login/login.spec(jasmine).js
new file mode 100644
index 000000000..6875376bc
--- /dev/null
+++ b/app/templates/e2e/account(auth)/login/login.spec(jasmine).js
@@ -0,0 +1,65 @@
+'use strict';
+
+var config = browser.params;<% if (filters.mongooseModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/sqldb').User;<% } %>
+
+describe('Login View', function() {
+  var page;
+
+  var loadPage = function() {
+    browser.get(config.baseUrl + '/login');
+    page = require('./login.po');
+  };
+
+  var testUser = {
+    name: 'Test User',
+    email: 'test@test.com',
+    password: 'test'
+  };
+
+  beforeEach(function(done) {
+    <% if (filters.mongooseModels) { %>UserModel.removeAsync()<% }
+       if (filters.sequelizeModels) { %>UserModel.destroy({ where: {} })<% } %>
+      .then(function() {
+        <% if (filters.mongooseModels) { %>return UserModel.createAsync(testUser);<% }
+           if (filters.sequelizeModels) { %>return UserModel.create(testUser);<% } %>
+      })
+      .then(loadPage)
+      .finally(done);
+  });
+
+  it('should include login form with correct inputs and submit button', function() {
+    expect(page.form.email.getAttribute('type')).toBe('email');
+    expect(page.form.email.getAttribute('name')).toBe('email');
+    expect(page.form.password.getAttribute('type')).toBe('password');
+    expect(page.form.password.getAttribute('name')).toBe('password');
+    expect(page.form.submit.getAttribute('type')).toBe('submit');
+    expect(page.form.submit.getText()).toBe('Login');
+  });
+
+  describe('with local auth', function() {
+
+    it('should login a user and redirecting to "/"', function() {
+      page.login(testUser);
+
+      var navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
+    });
+
+    it('should indicate login failures', function() {
+      page.login({
+        email: testUser.email,
+        password: 'badPassword'
+      });
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/login');
+
+      var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
+      expect(helpBlock.getText()).toBe('This password is not correct.');
+    });
+
+  });
+});
diff --git a/app/templates/e2e/account(auth)/login/login.spec(mocha).js b/app/templates/e2e/account(auth)/login/login.spec(mocha).js
new file mode 100644
index 000000000..a33970e67
--- /dev/null
+++ b/app/templates/e2e/account(auth)/login/login.spec(mocha).js
@@ -0,0 +1,77 @@
+'use strict';
+
+var config = browser.params;<% if (filters.mongooseModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/sqldb').User;<% } %>
+
+describe('Login View', function() {
+  var page;
+
+  var loadPage = function() {
+    browser.get(config.baseUrl + '/login');
+    page = require('./login.po');
+  };
+
+  var testUser = {
+    name: 'Test User',
+    email: 'test@test.com',
+    password: 'test'
+  };
+
+  before(function() {
+    return UserModel
+      <% if (filters.mongooseModels) { %>.removeAsync()<% }
+         if (filters.sequelizeModels) { %>.destroy({ where: {} })<% } %>
+      .then(function() {
+        <% if (filters.mongooseModels) { %>return UserModel.createAsync(testUser);<% }
+           if (filters.sequelizeModels) { %>return UserModel.create(testUser);<% } %>
+      })
+      .then(loadPage);
+  });
+
+  after(function() {
+    <% if (filters.mongooseModels) { %>return UserModel.removeAsync();<% }
+       if (filters.sequelizeModels) { %>return UserModel.destroy({ where: {} });<% } %>
+  });
+
+  it('should include login form with correct inputs and submit button', function() {
+    <%= does("page.form.email.getAttribute('type')") %>.eventually.equal('email');
+    <%= does("page.form.email.getAttribute('name')") %>.eventually.equal('email');
+    <%= does("page.form.password.getAttribute('type')") %>.eventually.equal('password');
+    <%= does("page.form.password.getAttribute('name')") %>.eventually.equal('password');
+    <%= does("page.form.submit.getAttribute('type')") %>.eventually.equal('submit');
+    <%= does("page.form.submit.getText()") %>.eventually.equal('Login');
+  });
+
+  describe('with local auth', function() {
+
+    it('should login a user and redirecting to "/"', function() {
+      page.login(testUser);
+
+      var navbar = require('../../components/navbar/navbar.po');
+
+      <%= does("browser.getCurrentUrl()") %>.eventually.equal(config.baseUrl + '/');
+      <%= does("navbar.navbarAccountGreeting.getText()") %>.eventually.equal('Hello ' + testUser.name);
+    });
+
+    describe('and invalid credentials', function() {
+      before(function() {
+        return loadPage();
+      })
+
+      it('should indicate login failures', function() {
+        page.login({
+          email: testUser.email,
+          password: 'badPassword'
+        });
+
+        <%= does("browser.getCurrentUrl()") %>.eventually.equal(config.baseUrl + '/login');
+
+        var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
+        <%= does("helpBlock.getText()") %>.eventually.equal('This password is not correct.');
+      });
+
+    });
+
+  });
+});
diff --git a/app/templates/e2e/account(auth)/logout/logout.spec(jasmine).js b/app/templates/e2e/account(auth)/logout/logout.spec(jasmine).js
new file mode 100644
index 000000000..cb727f2c5
--- /dev/null
+++ b/app/templates/e2e/account(auth)/logout/logout.spec(jasmine).js
@@ -0,0 +1,49 @@
+'use strict';
+
+var config = browser.params;<% if (filters.mongooseModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/sqldb').User;<% } %>
+
+describe('Logout View', function() {
+  var login = function(user) {
+    browser.get(config.baseUrl + '/login');
+    require('../login/login.po').login(user);
+  };
+
+  var testUser = {
+    name: 'Test User',
+    email: 'test@test.com',
+    password: 'test'
+  };
+
+  beforeEach(function(done) {
+    <% if (filters.mongooseModels) { %>UserModel.removeAsync()<% }
+       if (filters.sequelizeModels) { %>UserModel.destroy({ where: {} })<% } %>
+      .then(function() {
+        <% if (filters.mongooseModels) { %>return UserModel.createAsync(testUser);<% }
+           if (filters.sequelizeModels) { %>return UserModel.create(testUser);<% } %>
+      })
+      .then(function() {
+        return login(testUser);
+      })
+      .finally(done);
+  });
+
+  describe('with local auth', function() {
+
+    it('should logout a user and redirecting to "/"', function() {
+      var navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
+
+      browser.get(config.baseUrl + '/logout');
+
+      navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.isDisplayed()).toBe(false);
+    });
+
+  });
+});
diff --git a/app/templates/e2e/account(auth)/logout/logout.spec(mocha).js b/app/templates/e2e/account(auth)/logout/logout.spec(mocha).js
new file mode 100644
index 000000000..1e969ccf2
--- /dev/null
+++ b/app/templates/e2e/account(auth)/logout/logout.spec(mocha).js
@@ -0,0 +1,54 @@
+'use strict';
+
+var config = browser.params;<% if (filters.mongooseModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/sqldb').User;<% } %>
+
+describe('Logout View', function() {
+  var login = function(user) {
+    browser.get(config.baseUrl + '/login');
+    require('../login/login.po').login(user);
+  };
+
+  var testUser = {
+    name: 'Test User',
+    email: 'test@test.com',
+    password: 'test'
+  };
+
+  beforeEach(function() {
+    return UserModel
+      <% if (filters.mongooseModels) { %>.removeAsync()<% }
+         if (filters.sequelizeModels) { %>.destroy({ where: {} })<% } %>
+      .then(function() {
+        <% if (filters.mongooseModels) { %>return UserModel.createAsync(testUser);<% }
+           if (filters.sequelizeModels) { %>return UserModel.create(testUser);<% } %>
+      })
+      .then(function() {
+        return login(testUser);
+      });
+  });
+
+  after(function() {
+    <% if (filters.mongooseModels) { %>return UserModel.removeAsync();<% }
+       if (filters.sequelizeModels) { %>return UserModel.destroy({ where: {} });<% } %>
+  })
+
+  describe('with local auth', function() {
+
+    it('should logout a user and redirecting to "/"', function() {
+      var navbar = require('../../components/navbar/navbar.po');
+
+      <%= does("browser.getCurrentUrl()") %>.eventually.equal(config.baseUrl + '/');
+      <%= does("navbar.navbarAccountGreeting.getText()") %>.eventually.equal('Hello ' + testUser.name);
+
+      browser.get(config.baseUrl + '/logout');
+
+      navbar = require('../../components/navbar/navbar.po');
+
+      <%= does("browser.getCurrentUrl()") %>.eventually.equal(config.baseUrl + '/');
+      <%= does("navbar.navbarAccountGreeting.isDisplayed()") %>.eventually.equal(false);
+    });
+
+  });
+});
diff --git a/app/templates/e2e/account(auth)/signup/signup.po.js b/app/templates/e2e/account(auth)/signup/signup.po.js
new file mode 100644
index 000000000..3a496c6e3
--- /dev/null
+++ b/app/templates/e2e/account(auth)/signup/signup.po.js
@@ -0,0 +1,28 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var SignupPage = function() {
+  this.form = element(by.css('.form'));
+  this.form.name = this.form.element(by.model('user.name'));
+  this.form.email = this.form.element(by.model('user.email'));
+  this.form.password = this.form.element(by.model('user.password'));
+  this.form.submit = this.form.element(by.css('.btn-register'));
+
+  this.signup = function(data) {
+    for (var prop in data) {
+      var formElem = this.form[prop];
+      if (data.hasOwnProperty(prop) && formElem && typeof formElem.sendKeys === 'function') {
+        formElem.sendKeys(data[prop]);
+      }
+    }
+
+    this.form.submit.click();
+  };
+};
+
+module.exports = new SignupPage();
+
diff --git a/app/templates/e2e/account(auth)/signup/signup.spec(jasmine).js b/app/templates/e2e/account(auth)/signup/signup.spec(jasmine).js
new file mode 100644
index 000000000..e0a1230d5
--- /dev/null
+++ b/app/templates/e2e/account(auth)/signup/signup.spec(jasmine).js
@@ -0,0 +1,64 @@
+'use strict';
+
+var config = browser.params;<% if (filters.mongooseModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/sqldb').User;<% } %>
+
+describe('Signup View', function() {
+  var page;
+
+  var loadPage = function() {
+    browser.manage().deleteAllCookies();
+    browser.get(config.baseUrl + '/signup');
+    page = require('./signup.po');
+  };
+
+  var testUser = {
+    name: 'Test',
+    email: 'test@test.com',
+    password: 'test'
+  };
+
+  beforeEach(function() {
+    loadPage();
+  });
+
+  it('should include signup form with correct inputs and submit button', function() {
+    expect(page.form.name.getAttribute('type')).toBe('text');
+    expect(page.form.name.getAttribute('name')).toBe('name');
+    expect(page.form.email.getAttribute('type')).toBe('email');
+    expect(page.form.email.getAttribute('name')).toBe('email');
+    expect(page.form.password.getAttribute('type')).toBe('password');
+    expect(page.form.password.getAttribute('name')).toBe('password');
+    expect(page.form.submit.getAttribute('type')).toBe('submit');
+    expect(page.form.submit.getText()).toBe('Sign up');
+  });
+
+  describe('with local auth', function() {
+
+    beforeAll(function(done) {
+      <% if (filters.mongooseModels) { %>UserModel.removeAsync().then(done);<% }
+         if (filters.sequelizeModels) { %>UserModel.destroy({ where: {} }).then(done);<% } %>
+    });
+
+    it('should signup a new user, log them in, and redirecting to "/"', function() {
+      page.signup(testUser);
+
+      var navbar = require('../../components/navbar/navbar.po');
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
+      expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
+    });
+
+    it('should indicate signup failures', function() {
+      page.signup(testUser);
+
+      expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/signup');
+      expect(page.form.email.getAttribute('class')).toContain('ng-invalid-mongoose');
+
+      var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
+      expect(helpBlock.getText()).toBe('The specified email address is already in use.');
+    });
+
+  });
+});
diff --git a/app/templates/e2e/account(auth)/signup/signup.spec(mocha).js b/app/templates/e2e/account(auth)/signup/signup.spec(mocha).js
new file mode 100644
index 000000000..c0bade616
--- /dev/null
+++ b/app/templates/e2e/account(auth)/signup/signup.spec(mocha).js
@@ -0,0 +1,76 @@
+'use strict';
+
+var config = browser.params;<% if (filters.mongooseModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var UserModel = require(config.serverConfig.root + '/server/sqldb').User;<% } %>
+
+describe('Signup View', function() {
+  var page;
+
+  var loadPage = function() {
+    browser.manage().deleteAllCookies()
+    browser.get(config.baseUrl + '/signup');
+    page = require('./signup.po');
+  };
+
+  var testUser = {
+    name: 'Test',
+    email: 'test@test.com',
+    password: 'test'
+  };
+
+  before(function() {
+    return loadPage();
+  });
+
+  after(function() {
+    <% if (filters.mongooseModels) { %>return UserModel.removeAsync();<% }
+       if (filters.sequelizeModels) { %>return UserModel.destroy({ where: {} });<% } %>
+  });
+
+  it('should include signup form with correct inputs and submit button', function() {
+    <%= does("page.form.name.getAttribute('type')") %>.eventually.equal('text');
+    <%= does("page.form.name.getAttribute('name')") %>.eventually.equal('name');
+    <%= does("page.form.email.getAttribute('type')") %>.eventually.equal('email');
+    <%= does("page.form.email.getAttribute('name')") %>.eventually.equal('email');
+    <%= does("page.form.password.getAttribute('type')") %>.eventually.equal('password');
+    <%= does("page.form.password.getAttribute('name')") %>.eventually.equal('password');
+    <%= does("page.form.submit.getAttribute('type')") %>.eventually.equal('submit');
+    <%= does("page.form.submit.getText()") %>.eventually.equal('Sign up');
+  });
+
+  describe('with local auth', function() {
+
+    before(function() {
+      <% if (filters.mongooseModels) { %>return UserModel.removeAsync();<% }
+         if (filters.sequelizeModels) { %>return UserModel.destroy({ where: {} });<% } %>
+    })
+
+    it('should signup a new user, log them in, and redirecting to "/"', function() {
+      page.signup(testUser);
+
+      var navbar = require('../../components/navbar/navbar.po');
+
+      <%= does("browser.getCurrentUrl()") %>.eventually.equal(config.baseUrl + '/');
+      <%= does("navbar.navbarAccountGreeting.getText()") %>.eventually.equal('Hello ' + testUser.name);
+    });
+
+    describe('and invalid credentials', function() {
+      before(function() {
+        return loadPage();
+      });
+
+      it('should indicate signup failures', function() {
+        page.signup(testUser);
+
+        <%= does("browser.getCurrentUrl()") %>.eventually.equal(config.baseUrl + '/signup');
+        <%= does("page.form.email.getAttribute('class')") %>.eventually.contain('ng-invalid-mongoose');
+
+        var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
+        <%= does("helpBlock.getText()") %>.eventually.equal('The specified email address is already in use.');
+      });
+
+    });
+
+  });
+});
diff --git a/app/templates/e2e/components/navbar/navbar.po.js b/app/templates/e2e/components/navbar/navbar.po.js
new file mode 100644
index 000000000..80a48418e
--- /dev/null
+++ b/app/templates/e2e/components/navbar/navbar.po.js
@@ -0,0 +1,16 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var NavbarComponent = function() {
+  this.navbar = element(by.css('.navbar'));
+  this.navbarHeader = this.navbar.element(by.css('.navbar-header'));
+  this.navbarNav = this.navbar.element(by.css('#navbar-main .nav.navbar-nav:not(.navbar-right)'));<% if (filters.auth) { %>
+  this.navbarAccount = this.navbar.element(by.css('#navbar-main .nav.navbar-nav.navbar-right'));
+  this.navbarAccountGreeting = this.navbarAccount.element(by.binding('getCurrentUser().name'));<% } %>
+};
+
+module.exports = new NavbarComponent();
diff --git a/app/templates/e2e/main/main.spec.js b/app/templates/e2e/main/main.spec(jasmine).js
similarity index 70%
rename from app/templates/e2e/main/main.spec.js
rename to app/templates/e2e/main/main.spec(jasmine).js
index 61745a8de..57284495a 100644
--- a/app/templates/e2e/main/main.spec.js
+++ b/app/templates/e2e/main/main.spec(jasmine).js
@@ -1,16 +1,18 @@
 'use strict';
 
+var config = browser.params;
+
 describe('Main View', function() {
   var page;
 
   beforeEach(function() {
-    browser.get('/');
+    browser.get(config.baseUrl + '/');
     page = require('./main.po');
   });
 
   it('should include jumbotron with correct data', function() {
     expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
-    expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/);
+    expect(page.imgEl.getAttribute('src')).toMatch(/yeoman.png$/);
     expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
   });
 });
diff --git a/app/templates/e2e/main/main.spec(mocha).js b/app/templates/e2e/main/main.spec(mocha).js
new file mode 100644
index 000000000..4ea5c1012
--- /dev/null
+++ b/app/templates/e2e/main/main.spec(mocha).js
@@ -0,0 +1,18 @@
+'use strict';
+
+var config = browser.params;
+
+describe('Main View', function() {
+  var page;
+
+  beforeEach(function() {
+    browser.get(config.baseUrl + '/');
+    page = require('./main.po');
+  });
+
+  it('should include jumbotron with correct data', function() {
+    <%= does("page.h1El.getText()") %>.eventually.equal('\'Allo, \'Allo!');
+    <%= does("page.imgEl.getAttribute('src')") %>.eventually.match(/yeoman.png$/);
+    <%= does("page.imgEl.getAttribute('alt')") %>.eventually.equal('I\'m Yeoman');
+  });
+});
diff --git a/app/templates/karma.conf.js b/app/templates/karma.conf.js
index e7307a90a..9b46a3a22 100644
--- a/app/templates/karma.conf.js
+++ b/app/templates/karma.conf.js
@@ -6,22 +6,21 @@ module.exports = function(config) {
     // base path, that will be used to resolve files and exclude
     basePath: '',
 
-    // testing framework to use (jasmine/mocha/qunit/...)
-    frameworks: ['jasmine'],
+    // testing framework to use (jasmine/mocha/qunit/...)<% if (filters.jasmine) { %>
+    frameworks: ['jasmine'],<% } if (filters.mocha) { %>
+    frameworks: ['mocha', 'chai', 'sinon-chai', 'chai-as-promised', 'chai-things'],
+
+    client: {
+      mocha: {
+        timeout: 5000 // set default mocha spec timeout
+      }
+    },<% } %>
 
     // list of files / patterns to load in the browser
     files: [
-      'client/bower_components/jquery/dist/jquery.js',
-      'client/bower_components/angular/angular.js',
-      'client/bower_components/angular-mocks/angular-mocks.js',
-      'client/bower_components/angular-resource/angular-resource.js',
-      'client/bower_components/angular-cookies/angular-cookies.js',
-      'client/bower_components/angular-sanitize/angular-sanitize.js',
-      'client/bower_components/angular-route/angular-route.js',<% if(filters.uibootstrap) { %>
-      'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',<% } %>
-      'client/bower_components/lodash/dist/lodash.compat.js',<% if(filters.socketio) { %>
-      'client/bower_components/angular-socket-io/socket.js',<% } %><% if(filters.uirouter) { %>
-      'client/bower_components/angular-ui-router/release/angular-ui-router.js',<% } %>
+      // bower:js
+      // endbower<% if (filters.socketio) { %>
+      'node_modules/socket.io-client/socket.io.js',<% } %>
       'client/app/app.js',
       'client/app/app.coffee',
       'client/app/**/*.js',
@@ -37,7 +36,7 @@ module.exports = function(config) {
     preprocessors: {
       '**/*.jade': 'ng-jade2js',
       '**/*.html': 'html2js',<% if(filters.babel) { %>
-      'client/app/**/*.js': 'babel',<% } %>
+      'client/{app,components}/**/*.js': 'babel',<% } %>
       '**/*.coffee': 'coffee',
     },
 
@@ -73,6 +72,14 @@ module.exports = function(config) {
     // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
     logLevel: config.LOG_INFO,
 
+    // reporter types:
+    // - dots
+    // - progress (default)
+    // - spec (karma-spec-reporter)
+    // - junit
+    // - growl
+    // - coverage
+    reporters: ['spec'],
 
     // enable / disable watching file and executing tests whenever any file changes
     autoWatch: false,
diff --git a/app/templates/mocha.conf.js b/app/templates/mocha.conf.js
new file mode 100644
index 000000000..76f56625e
--- /dev/null
+++ b/app/templates/mocha.conf.js
@@ -0,0 +1,19 @@
+'use strict';
+
+// Register the Babel require hook
+require('babel-core/register');
+
+var chai = require('chai');
+
+// Load Chai assertions
+global.expect = chai.expect;
+global.assert = chai.assert;
+chai.should();
+
+// Load Sinon
+global.sinon = require('sinon');
+
+// Initialize Chai plugins
+chai.use(require('sinon-chai'));
+chai.use(require('chai-as-promised'));
+chai.use(require('chai-things'))
diff --git a/app/templates/protractor.conf.js b/app/templates/protractor.conf.js
index cb66c67c1..6178c1a76 100644
--- a/app/templates/protractor.conf.js
+++ b/app/templates/protractor.conf.js
@@ -3,7 +3,7 @@
 
 'use strict';
 
-exports.config = {
+var config = {
   // The timeout for each script run on the browser. This should be longer
   // than the maximum time your application needs to stabilize between tasks.
   allScriptsTimeout: 110000,
@@ -12,9 +12,10 @@ exports.config = {
   // with relative paths will be prepended with this.
   baseUrl: 'http://localhost:' + (process.env.PORT || '9000'),
 
-  // If true, only chromedriver will be started, not a standalone selenium.
-  // Tests for browsers other than chrome will not run.
-  chromeOnly: true,
+  // Credientials for Saucelabs
+  sauceUser: process.env.SAUCE_USERNAME,
+
+  sauceKey: process.env.SAUCE_ACCESS_KEY,
 
   // list of files / patterns to load in the browser
   specs: [
@@ -31,7 +32,10 @@ exports.config = {
   // and
   // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
   capabilities: {
-    'browserName': 'chrome'
+    'browserName': 'chrome',
+    'name': 'Fullstack E2E',
+    'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
+    'build': process.env.TRAVIS_BUILD_NUMBER
   },
 
   // ----- The test framework -----
@@ -39,12 +43,49 @@ exports.config = {
   // Jasmine and Cucumber are fully supported as a test and assertion framework.
   // Mocha has limited beta support. You will need to include your own
   // assertion framework if working with mocha.
-  framework: 'jasmine',
-
+  framework: '<% if (filters.jasmine) { %>jasmine2<% } if (filters.mocha) { %>mocha<% } %>',
+<% if (filters.jasmine) { %>
   // ----- Options to be passed to minijasminenode -----
   //
-  // See the full list at https://github.com/juliemr/minijasminenode
+  // See the full list at https://github.com/jasmine/jasmine-npm
   jasmineNodeOpts: {
+    defaultTimeoutInterval: 30000,
+    print: function() {}  // for jasmine-spec-reporter
+  },<% } if (filters.mocha) { %>
+  // ----- Options to be passed to mocha -----
+  mochaOpts: {
+    reporter: 'spec',
+    timeout: 30000,
     defaultTimeoutInterval: 30000
+  },<% } %>
+
+  // Prepare environment for tests
+  params: {
+    serverConfig: require('./server/config/environment')
+  },
+
+  onPrepare: function() {<% if (filters.mocha) { %>
+    // Load Mocha and Chai + plugins
+    require('./mocha.conf');
+
+    // Expose should assertions (see https://github.com/angular/protractor/issues/633)
+    Object.defineProperty(
+      protractor.promise.Promise.prototype,
+      'should',
+      Object.getOwnPropertyDescriptor(Object.prototype, 'should')
+    );
+<% } if (filters.jasmine) { %>
+    var SpecReporter = require('jasmine-spec-reporter');
+    // add jasmine spec reporter
+    jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: true}));
+<% } %>
+    var serverConfig = config.params.serverConfig;<% if (filters.mongoose) { %>
+
+    // Setup mongo for tests
+    var mongoose = require('mongoose');
+    mongoose.connect(serverConfig.mongo.uri, serverConfig.mongo.options); // Connect to database<% } %>
   }
 };
+
+config.params.baseUrl = config.baseUrl;
+exports.config = config;
diff --git a/app/templates/server/.jshintrc b/app/templates/server/.jshintrc
index 66d1af7c9..69f3b00e3 100644
--- a/app/templates/server/.jshintrc
+++ b/app/templates/server/.jshintrc
@@ -1,4 +1,5 @@
 {
+  "expr": true,
   "node": true,
   "esnext": true,
   "bitwise": true,
diff --git a/app/templates/server/.jshintrc-spec b/app/templates/server/.jshintrc-spec
index b6b55cbf9..b9390c374 100644
--- a/app/templates/server/.jshintrc-spec
+++ b/app/templates/server/.jshintrc-spec
@@ -6,6 +6,9 @@
     "before": true,
     "beforeEach": true,
     "after": true,
-    "afterEach": true
+    "afterEach": true,
+    "expect": true,
+    "assert": true,
+    "sinon": true
   }
 }
diff --git a/app/templates/server/api/thing/index.js b/app/templates/server/api/thing/index.js
deleted file mode 100644
index 242ed5901..000000000
--- a/app/templates/server/api/thing/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict';
-
-var express = require('express');
-var controller = require('./thing.controller');
-
-var router = express.Router();
-
-router.get('/', controller.index);<% if(filters.mongoose) { %>
-router.get('/:id', controller.show);
-router.post('/', controller.create);
-router.put('/:id', controller.update);
-router.patch('/:id', controller.update);
-router.delete('/:id', controller.destroy);<% } %>
-
-module.exports = router;
\ No newline at end of file
diff --git a/app/templates/server/api/thing/thing.controller.js b/app/templates/server/api/thing/thing.controller.js
deleted file mode 100644
index 0adc6211c..000000000
--- a/app/templates/server/api/thing/thing.controller.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * Using Rails-like standard naming convention for endpoints.
- * GET     /things              ->  index
- * POST    /things              ->  create
- * GET     /things/:id          ->  show
- * PUT     /things/:id          ->  update
- * DELETE  /things/:id          ->  destroy
- */
-
-'use strict';
-
-var _ = require('lodash');<% if (filters.mongoose) { %>
-var Thing = require('./thing.model');<% } %>
-
-// Get list of things
-exports.index = function(req, res) {<% if (!filters.mongoose) { %>
-  res.json([
-  {
-  name : 'Development Tools',
-  info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.'
-  }, {
-  name : 'Server and Client integration',
-  info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.'
-  }, {
-  name : 'Smart Build System',
-  info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html'
-  },  {
-  name : 'Modular Structure',
-  info : 'Best practice client and server structures allow for more code reusability and maximum scalability'
-  },  {
-  name : 'Optimized Build',
-  info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.'
-  },{
-  name : 'Deployment Ready',
-  info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
-  }
-  ]);<% } %><% if (filters.mongoose) { %>
-  Thing.find(function (err, things) {
-    if(err) { return handleError(res, err); }
-    return res.status(200).json(things);
-  });<% } %>
-};<% if (filters.mongoose) { %>
-
-// Get a single thing
-exports.show = function(req, res) {
-  Thing.findById(req.params.id, function (err, thing) {
-    if(err) { return handleError(res, err); }
-    if(!thing) { return res.status(404).send('Not Found'); }
-    return res.json(thing);
-  });
-};
-
-// Creates a new thing in the DB.
-exports.create = function(req, res) {
-  Thing.create(req.body, function(err, thing) {
-    if(err) { return handleError(res, err); }
-    return res.status(201).json(thing);
-  });
-};
-
-// Updates an existing thing in the DB.
-exports.update = function(req, res) {
-  if(req.body._id) { delete req.body._id; }
-  Thing.findById(req.params.id, function (err, thing) {
-    if (err) { return handleError(res, err); }
-    if(!thing) { return res.status(404).send('Not Found'); }
-    var updated = _.merge(thing, req.body);
-    updated.save(function (err) {
-      if (err) { return handleError(res, err); }
-      return res.status(200).json(thing);
-    });
-  });
-};
-
-// Deletes a thing from the DB.
-exports.destroy = function(req, res) {
-  Thing.findById(req.params.id, function (err, thing) {
-    if(err) { return handleError(res, err); }
-    if(!thing) { return res.status(404).send('Not Found'); }
-    thing.remove(function(err) {
-      if(err) { return handleError(res, err); }
-      return res.status(204).send('No Content');
-    });
-  });
-};
-
-function handleError(res, err) {
-  return res.status(500).send(err);
-}<% } %>
\ No newline at end of file
diff --git a/app/templates/server/api/thing/thing.model(mongoose).js b/app/templates/server/api/thing/thing.model(mongoose).js
deleted file mode 100644
index ed857cd3b..000000000
--- a/app/templates/server/api/thing/thing.model(mongoose).js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use strict';
-
-var mongoose = require('mongoose'),
-    Schema = mongoose.Schema;
-
-var ThingSchema = new Schema({
-  name: String,
-  info: String,
-  active: Boolean
-});
-
-module.exports = mongoose.model('Thing', ThingSchema);
\ No newline at end of file
diff --git a/app/templates/server/api/thing/thing.socket(socketio).js b/app/templates/server/api/thing/thing.socket(socketio).js
deleted file mode 100644
index 79d327695..000000000
--- a/app/templates/server/api/thing/thing.socket(socketio).js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Broadcast updates to client when the model changes
- */
-
-'use strict';
-
-var thing = require('./thing.model');
-
-exports.register = function(socket) {
-  thing.schema.post('save', function (doc) {
-    onSave(socket, doc);
-  });
-  thing.schema.post('remove', function (doc) {
-    onRemove(socket, doc);
-  });
-}
-
-function onSave(socket, doc, cb) {
-  socket.emit('thing:save', doc);
-}
-
-function onRemove(socket, doc, cb) {
-  socket.emit('thing:remove', doc);
-}
\ No newline at end of file
diff --git a/app/templates/server/api/thing/thing.spec.js b/app/templates/server/api/thing/thing.spec.js
deleted file mode 100644
index 17c8c6cd0..000000000
--- a/app/templates/server/api/thing/thing.spec.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-var should = require('should');
-var app = require('../../app');
-var request = require('supertest');
-
-describe('GET /api/things', function() {
-
-  it('should respond with JSON array', function(done) {
-    request(app)
-      .get('/api/things')
-      .expect(200)
-      .expect('Content-Type', /json/)
-      .end(function(err, res) {
-        if (err) return done(err);
-        res.body.should.be.instanceof(Array);
-        done();
-      });
-  });
-});
diff --git a/app/templates/server/api/user(auth)/index.js b/app/templates/server/api/user(auth)/index.js
index 48567e485..be6fd3af3 100644
--- a/app/templates/server/api/user(auth)/index.js
+++ b/app/templates/server/api/user(auth)/index.js
@@ -2,7 +2,6 @@
 
 var express = require('express');
 var controller = require('./user.controller');
-var config = require('../../config/environment');
 var auth = require('../../auth/auth.service');
 
 var router = express.Router();
diff --git a/app/templates/server/api/user(auth)/index.spec.js b/app/templates/server/api/user(auth)/index.spec.js
new file mode 100644
index 000000000..d2ee914bd
--- /dev/null
+++ b/app/templates/server/api/user(auth)/index.spec.js
@@ -0,0 +1,107 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var userCtrlStub = {
+  index: 'userCtrl.index',
+  destroy: 'userCtrl.destroy',
+  me: 'userCtrl.me',
+  changePassword: 'userCtrl.changePassword',
+  show: 'userCtrl.show',
+  create: 'userCtrl.create'
+};
+
+var authServiceStub = {
+  isAuthenticated: function() {
+    return 'authService.isAuthenticated';
+  },
+  hasRole: function(role) {
+    return 'authService.hasRole.' + role;
+  }
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var userIndex = proxyquire('./index', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './user.controller': userCtrlStub,
+  '../../auth/auth.service': authServiceStub
+});
+
+describe('User API Router:', function() {
+
+  it('should return an express router instance', function() {
+    userIndex.should.equal(routerStub);
+  });
+
+  describe('GET /api/users', function() {
+
+    it('should verify admin role and route to user.controller.index', function() {
+      routerStub.get
+                .withArgs('/', 'authService.hasRole.admin', 'userCtrl.index')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/users/:id', function() {
+
+    it('should verify admin role and route to user.controller.destroy', function() {
+      routerStub.delete
+                .withArgs('/:id', 'authService.hasRole.admin', 'userCtrl.destroy')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/users/me', function() {
+
+    it('should be authenticated and route to user.controller.me', function() {
+      routerStub.get
+                .withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/users/:id/password', function() {
+
+    it('should be authenticated and route to user.controller.changePassword', function() {
+      routerStub.put
+                .withArgs('/:id/password', 'authService.isAuthenticated', 'userCtrl.changePassword')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/users/:id', function() {
+
+    it('should be authenticated and route to user.controller.show', function() {
+      routerStub.get
+                .withArgs('/:id', 'authService.isAuthenticated', 'userCtrl.show')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/users', function() {
+
+    it('should route to user.controller.create', function() {
+      routerStub.post
+                .withArgs('/', 'userCtrl.create')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/app/templates/server/api/user(auth)/user.controller.js b/app/templates/server/api/user(auth)/user.controller.js
index 585e47b67..f1c2498fb 100644
--- a/app/templates/server/api/user(auth)/user.controller.js
+++ b/app/templates/server/api/user(auth)/user.controller.js
@@ -1,50 +1,98 @@
 'use strict';
-
-var User = require('./user.model');
+<% if (filters.mongooseModels) { %>
+var User = require('./user.model');<% } %><% if (filters.sequelizeModels) { %>
+var _ = require('lodash');
+var sqldb = require('../../sqldb');
+var User = sqldb.User;<% } %>
 var passport = require('passport');
 var config = require('../../config/environment');
 var jwt = require('jsonwebtoken');
 
-var validationError = function(res, err) {
-  return res.status(422).json(err);
-};
+function validationError(res, statusCode) {
+  statusCode = statusCode || 422;
+  return function(err) {
+    res.status(statusCode).json(err);
+  }
+}
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function respondWith(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function() {
+    res.status(statusCode).end();
+  };
+}
 
 /**
  * Get list of users
  * restriction: 'admin'
  */
 exports.index = function(req, res) {
-  User.find({}, '-salt -hashedPassword', function (err, users) {
-    if(err) return res.status(500).send(err);
-    res.status(200).json(users);
-  });
+  <% if (filters.mongooseModels) { %>User.findAsync({}, '-salt -hashedPassword')<% }
+     if (filters.sequelizeModels) { %>User.findAll({
+    attributes: [
+      '_id',
+      'name',
+      'email',
+      'role',
+      'provider'
+    ]
+  })<% } %>
+    .then(function(users) {
+      res.status(200).json(users);
+    })
+    .catch(handleError(res));
 };
 
 /**
  * Creates a new user
  */
-exports.create = function (req, res, next) {
-  var newUser = new User(req.body);
+exports.create = function(req, res, next) {
+  <% if (filters.mongooseModels) { %>var newUser = new User(req.body);
   newUser.provider = 'local';
   newUser.role = 'user';
-  newUser.save(function(err, user) {
-    if (err) return validationError(res, err);
-    var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 });
-    res.json({ token: token });
-  });
+  newUser.saveAsync()<% }
+     if (filters.sequelizeModels) { %>var newUser = User.build(req.body);
+  newUser.setDataValue('provider', 'local');
+  newUser.setDataValue('role', 'user');
+  newUser.save()<% } %>
+    <% if (filters.mongooseModels) { %>.spread(function(user) {<% }
+       if (filters.sequelizeModels) { %>.then(function(user) {<% } %>
+      var token = jwt.sign({ _id: user._id }, config.secrets.session, {
+        expiresInMinutes: 60 * 5
+      });
+      res.json({ token: token });
+    })
+    .catch(validationError(res));
 };
 
 /**
  * Get a single user
  */
-exports.show = function (req, res, next) {
+exports.show = function(req, res, next) {
   var userId = req.params.id;
 
-  User.findById(userId, function (err, user) {
-    if (err) return next(err);
-    if (!user) return res.status(401).send('Unauthorized');
-    res.json(user.profile);
-  });
+  <% if (filters.mongooseModels) { %>User.findByIdAsync(userId)<% }
+     if (filters.sequelizeModels) { %>User.find({
+    where: {
+      _id: userId
+    }
+  })<% } %>
+    .then(function(user) {
+      if (!user) {
+        return res.status(404).end();
+      }
+      res.json(user.profile);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
 };
 
 /**
@@ -52,10 +100,12 @@ exports.show = function (req, res, next) {
  * restriction: 'admin'
  */
 exports.destroy = function(req, res) {
-  User.findByIdAndRemove(req.params.id, function(err, user) {
-    if(err) return res.status(500).send(err);
-    return res.status(204).send('No Content');
-  });
+  <% if (filters.mongooseModels) { %>User.findByIdAndRemoveAsync(req.params.id)<% }
+     if (filters.sequelizeModels) { %>User.destroy({ _id: req.params.id })<% } %>
+    .then(function() {
+      res.status(204).end();
+    })
+    .catch(handleError(res));
 };
 
 /**
@@ -66,17 +116,25 @@ exports.changePassword = function(req, res, next) {
   var oldPass = String(req.body.oldPassword);
   var newPass = String(req.body.newPassword);
 
-  User.findById(userId, function (err, user) {
-    if(user.authenticate(oldPass)) {
-      user.password = newPass;
-      user.save(function(err) {
-        if (err) return validationError(res, err);
-        res.status(200).send('OK');
-      });
-    } else {
-      res.status(403).send('Forbidden');
+  <% if (filters.mongooseModels) { %>User.findByIdAsync(userId)<% }
+     if (filters.sequelizeModels) { %>User.find({
+    where: {
+      _id: userId
     }
-  });
+  })<% } %>
+    .then(function(user) {
+      if (user.authenticate(oldPass)) {
+        user.password = newPass;
+        <% if (filters.mongooseModels) { %>return user.saveAsync()<% }
+           if (filters.sequelizeModels) { %>return user.save()<% } %>
+          .then(function() {
+            res.status(204).end();
+          })
+          .catch(validationError(res));
+      } else {
+        return res.status(403).end();
+      }
+    });
 };
 
 /**
@@ -84,13 +142,29 @@ exports.changePassword = function(req, res, next) {
  */
 exports.me = function(req, res, next) {
   var userId = req.user._id;
-  User.findOne({
-    _id: userId
-  }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt
-    if (err) return next(err);
-    if (!user) return res.status(401).send('Unauthorized');
-    res.json(user);
-  });
+
+  <% if (filters.mongooseModels) { %>User.findOneAsync({ _id: userId }, '-salt -hashedPassword')<% }
+     if (filters.sequelizeModels) { %>User.find({
+    where: {
+      _id: userId
+    },
+    attributes: [
+      '_id',
+      'name',
+      'email',
+      'role',
+      'provider'
+    ]
+  })<% } %>
+    .then(function(user) { // don't ever give out the password or salt
+      if (!user) {
+        return res.status(401).end();
+      }
+      res.json(user);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
 };
 
 /**
diff --git a/app/templates/server/api/user(auth)/user.events.js b/app/templates/server/api/user(auth)/user.events.js
new file mode 100644
index 000000000..102fd5d55
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.events.js
@@ -0,0 +1,41 @@
+/**
+ * User model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;<% if (filters.mongooseModels) { %>
+var User = require('./user.model');<% } if (filters.sequelizeModels) { %>
+var User = require('../../sqldb').User;<% } %>
+var UserEvents = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+UserEvents.setMaxListeners(0);
+
+// Model events<% if (filters.mongooseModels) { %>
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};<% } if (filters.sequelizeModels) { %>
+var events = {
+  'afterCreate': 'save',
+  'afterUpdate': 'save',
+  'afterDestroy': 'remove'
+};<% } %>
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];<% if (filters.mongooseModels) { %>
+  User.schema.post(e, emitEvent(event));<% } if (filters.sequelizeModels) { %>
+  User.hook(e, emitEvent(event));<% } %>
+}
+
+function emitEvent(event) {
+  return function(doc<% if (filters.sequelizeModels) { %>, options, done<% } %>) {
+    UserEvents.emit(event + ':' + doc._id, doc);
+    UserEvents.emit(event, doc);<% if (filters.sequelizeModels) { %>
+    done(null);<% } %>
+  }
+}
+
+module.exports = UserEvents;
diff --git a/app/templates/server/api/user(auth)/user.integration.js b/app/templates/server/api/user(auth)/user.integration.js
new file mode 100644
index 000000000..19978ce48
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.integration.js
@@ -0,0 +1,70 @@
+'use strict';
+
+var app = require('../..');<% if (filters.mongooseModels) { %>
+var User = require('./user.model');<% } %><% if (filters.sequelizeModels) { %>
+var User = require('../../sqldb').User;<% } %>
+var request = require('supertest');
+
+describe('User API:', function() {
+  var user;
+
+  // Clear users before testing
+  before(function() {
+    return <% if (filters.mongooseModels) { %>User.removeAsync().then(function() {<% }
+       if (filters.sequelizeModels) { %>User.destroy({ where: {} }).then(function() {<% } %>
+      <% if (filters.mongooseModels) { %>user = new User({<% }
+         if (filters.sequelizeModels) { %>user = User.build({<% } %>
+        name: 'Fake User',
+        email: 'test@test.com',
+        password: 'password'
+      });
+
+      return <% if (filters.mongooseModels) { %>user.saveAsync();<% }
+         if (filters.sequelizeModels) { %>user.save();<% } %>
+    });
+  });
+
+  // Clear users after testing
+  after(function() {
+    <% if (filters.mongooseModels) { %>return User.removeAsync();<% }
+       if (filters.sequelizeModels) { %>return User.destroy({ where: {} });<% } %>
+  });
+
+  describe('GET /api/users/me', function() {
+    var token;
+
+    before(function(done) {
+      request(app)
+        .post('/auth/local')
+        .send({
+          email: 'test@test.com',
+          password: 'password'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          token = res.body.token;
+          done();
+        });
+    });
+
+    it('should respond with a user profile when authenticated', function(done) {
+      request(app)
+        .get('/api/users/me')
+        .set('authorization', 'Bearer ' + token)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          res.body._id.toString().should.equal(user._id.toString());
+          done();
+        });
+    });
+
+    it('should respond with a 401 when not authenticated', function(done) {
+      request(app)
+        .get('/api/users/me')
+        .expect(401)
+        .end(done);
+    });
+  });
+});
diff --git a/app/templates/server/api/user(auth)/user.model(mongooseModels).js b/app/templates/server/api/user(auth)/user.model(mongooseModels).js
new file mode 100644
index 000000000..f8fa923cf
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.model(mongooseModels).js
@@ -0,0 +1,228 @@
+'use strict';
+
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
+var crypto = require('crypto');<% if (filters.oauth) { %>
+var authTypes = ['github', 'twitter', 'facebook', 'google'];<% } %>
+
+var UserSchema = new Schema({
+  name: String,
+  email: {
+    type: String,
+    lowercase: true
+  },
+  role: {
+    type: String,
+    default: 'user'
+  },
+  password: String,
+  provider: String,
+  salt: String<% if (filters.oauth) { %>,<% if (filters.facebookAuth) { %>
+  facebook: {},<% } %><% if (filters.twitterAuth) { %>
+  twitter: {},<% } %><% if (filters.googleAuth) { %>
+  google: {},<% } %>
+  github: {}<% } %>
+});
+
+/**
+ * Virtuals
+ */
+
+// Public profile information
+UserSchema
+  .virtual('profile')
+  .get(function() {
+    return {
+      'name': this.name,
+      'role': this.role
+    };
+  });
+
+// Non-sensitive info we'll be putting in the token
+UserSchema
+  .virtual('token')
+  .get(function() {
+    return {
+      '_id': this._id,
+      'role': this.role
+    };
+  });
+
+/**
+ * Validations
+ */
+
+// Validate empty email
+UserSchema
+  .path('email')
+  .validate(function(email) {<% if (filters.oauth) { %>
+    if (authTypes.indexOf(this.provider) !== -1) {
+      return true;
+    }<% } %>
+    return email.length;
+  }, 'Email cannot be blank');
+
+// Validate empty password
+UserSchema
+  .path('password')
+  .validate(function(password) {<% if (filters.oauth) { %>
+    if (authTypes.indexOf(this.provider) !== -1) {
+      return true;
+    }<% } %>
+    return password.length;
+  }, 'Password cannot be blank');
+
+// Validate email is not taken
+UserSchema
+  .path('email')
+  .validate(function(value, respond) {
+    var self = this;
+    return this.constructor.findOneAsync({ email: value })
+      .then(function(user) {
+        if (user) {
+          if (self.id === user.id) {
+            return respond(true);
+          }
+          return respond(false);
+        }
+        return respond(true);
+      })
+      .catch(function(err) {
+        throw err;
+      });
+  }, 'The specified email address is already in use.');
+
+var validatePresenceOf = function(value) {
+  return value && value.length;
+};
+
+/**
+ * Pre-save hook
+ */
+UserSchema
+  .pre('save', function(next) {
+    // Handle new/update passwords
+    if (this.isModified('password')) {
+      if (!validatePresenceOf(this.password)<% if (filters.oauth) { %> && authTypes.indexOf(this.provider) === -1<% } %>) {
+        next(new Error('Invalid password'));
+      }
+
+      // Make salt with a callback
+      var _this = this;
+      this.makeSalt(function(saltErr, salt) {
+        if (saltErr) {
+          next(saltErr);
+        }
+        _this.salt = salt;
+        _this.encryptPassword(_this.password, function(encryptErr, hashedPassword) {
+          if (encryptErr) {
+            next(encryptErr);
+          }
+          _this.password = hashedPassword;
+          next();
+        });
+      });
+    } else {
+      next();
+    }
+  });
+
+/**
+ * Methods
+ */
+UserSchema.methods = {
+  /**
+   * Authenticate - check if the passwords are the same
+   *
+   * @param {String} password
+   * @param {Function} callback
+   * @return {Boolean}
+   * @api public
+   */
+  authenticate: function(password, callback) {
+    if (!callback) {
+      return this.password === this.encryptPassword(password);
+    }
+
+    var _this = this;
+    this.encryptPassword(password, function(err, pwdGen) {
+      if (err) {
+        callback(err);
+      }
+
+      if (_this.password === pwdGen) {
+        callback(null, true);
+      }
+      else {
+        callback(null, false);
+      }
+    });
+  },
+
+  /**
+   * Make salt
+   *
+   * @param {Number} byteSize Optional salt byte size, default to 16
+   * @param {Function} callback
+   * @return {String}
+   * @api public
+   */
+  makeSalt: function(byteSize, callback) {
+    var defaultByteSize = 16;
+
+    if (typeof arguments[0] === 'function') {
+      callback = arguments[0];
+      byteSize = defaultByteSize;
+    }
+    else if (typeof arguments[1] === 'function') {
+      callback = arguments[1];
+    }
+
+    if (!byteSize) {
+      byteSize = defaultByteSize;
+    }
+
+    if (!callback) {
+      return crypto.randomBytes(byteSize).toString('base64');
+    }
+
+    return crypto.randomBytes(byteSize, function(err, salt) {
+      if (err) {
+        callback(err);
+      }
+      return callback(null, salt.toString('base64'));
+    });
+  },
+
+  /**
+   * Encrypt password
+   *
+   * @param {String} password
+   * @param {Function} callback
+   * @return {String}
+   * @api public
+   */
+  encryptPassword: function(password, callback) {
+    if (!password || !this.salt) {
+      return null;
+    }
+
+    var defaultIterations = 10000;
+    var defaultKeyLength = 64;
+    var salt = new Buffer(this.salt, 'base64');
+
+    if (!callback) {
+      return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength)
+                   .toString('base64');
+    }
+
+    return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength, function(err, key) {
+      if (err) {
+        callback(err);
+      }
+      return callback(null, key.toString('base64'));
+    });
+  }
+};
+
+module.exports = mongoose.model('User', UserSchema);
diff --git a/app/templates/server/api/user(auth)/user.model(sequelizeModels).js b/app/templates/server/api/user(auth)/user.model(sequelizeModels).js
new file mode 100644
index 000000000..776eafc3e
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.model(sequelizeModels).js
@@ -0,0 +1,236 @@
+'use strict';
+
+var crypto = require('crypto');<% if (filters.oauth) { %>
+var authTypes = ['github', 'twitter', 'facebook', 'google'];<% } %>
+
+var validatePresenceOf = function(value) {
+  return value && value.length;
+};
+
+module.exports = function(sequelize, DataTypes) {
+  var User = sequelize.define('User', {
+
+    _id: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+      primaryKey: true,
+      autoIncrement: true
+    },
+    name: DataTypes.STRING,
+    email: {
+      type: DataTypes.STRING,
+      unique: {
+        msg: 'The specified email address is already in use.'
+      },
+      validate: {
+        isEmail: true
+      }
+    },
+    role: {
+      type: DataTypes.STRING,
+      defaultValue: 'user'
+    },
+    password: {
+      type: DataTypes.STRING,
+      validate: {
+        notEmpty: true
+      }
+    },
+    provider: DataTypes.STRING,
+    salt: DataTypes.STRING<% if (filters.oauth) { %>,<% if (filters.facebookAuth) { %>
+    facebook: DataTypes.TEXT,<% } %><% if (filters.twitterAuth) { %>
+    twitter: DataTypes.TEXT,<% } %><% if (filters.googleAuth) { %>
+    google: DataTypes.TEXT,<% } %>
+    github: DataTypes.TEXT<% } %>
+
+  }, {
+
+    /**
+     * Virtual Getters
+     */
+    getterMethods: {
+      // Public profile information
+      profile: function() {
+        return {
+          'name': this.name,
+          'role': this.role
+        };
+      },
+
+      // Non-sensitive info we'll be putting in the token
+      token: function() {
+        return {
+          '_id': this._id,
+          'role': this.role
+        };
+      }
+    },
+
+    /**
+     * Pre-save hooks
+     */
+    hooks: {
+      beforeBulkCreate: function(users, fields, fn) {
+        var totalUpdated = 0;
+        users.forEach(function(user) {
+          user.updatePassword(function(err) {
+            if (err) {
+              return fn(err);
+            }
+            totalUpdated += 1;
+            if (totalUpdated === users.length) {
+              return fn();
+            }
+          });
+        });
+      },
+      beforeCreate: function(user, fields, fn) {
+        user.updatePassword(fn);
+      },
+      beforeUpdate: function(user, fields, fn) {
+        if (user.changed('password')) {
+          return user.updatePassword(fn);
+        }
+        fn();
+      }
+    },
+
+    /**
+     * Instance Methods
+     */
+    instanceMethods: {
+      /**
+       * Authenticate - check if the passwords are the same
+       *
+       * @param {String} password
+       * @param {Function} callback
+       * @return {Boolean}
+       * @api public
+       */
+      authenticate: function(password, callback) {
+        if (!callback) {
+          return this.password === this.encryptPassword(password);
+        }
+
+        var _this = this;
+        this.encryptPassword(password, function(err, pwdGen) {
+          if (err) {
+            callback(err);
+          }
+
+          if (_this.password === pwdGen) {
+            callback(null, true);
+          }
+          else {
+            callback(null, false);
+          }
+        });
+      },
+
+      /**
+       * Make salt
+       *
+       * @param {Number} byteSize Optional salt byte size, default to 16
+       * @param {Function} callback
+       * @return {String}
+       * @api public
+       */
+      makeSalt: function(byteSize, callback) {
+        var defaultByteSize = 16;
+
+        if (typeof arguments[0] === 'function') {
+          callback = arguments[0];
+          byteSize = defaultByteSize;
+        }
+        else if (typeof arguments[1] === 'function') {
+          callback = arguments[1];
+        }
+
+        if (!byteSize) {
+          byteSize = defaultByteSize;
+        }
+
+        if (!callback) {
+          return crypto.randomBytes(byteSize).toString('base64');
+        }
+
+        return crypto.randomBytes(byteSize, function(err, salt) {
+          if (err) {
+            callback(err);
+          }
+          return callback(null, salt.toString('base64'));
+        });
+      },
+
+      /**
+       * Encrypt password
+       *
+       * @param {String} password
+       * @param {Function} callback
+       * @return {String}
+       * @api public
+       */
+      encryptPassword: function(password, callback) {
+        if (!password || !this.salt) {
+          if (!callback) {
+            return null;
+          }
+          return callback(null);
+        }
+
+        var defaultIterations = 10000;
+        var defaultKeyLength = 64;
+        var salt = new Buffer(this.salt, 'base64');
+
+        if (!callback) {
+          return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength)
+                       .toString('base64');
+        }
+
+        return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength,
+          function(err, key) {
+            if (err) {
+              callback(err);
+            }
+            return callback(null, key.toString('base64'));
+          });
+      },
+
+      /**
+       * Update password field
+       *
+       * @param {Function} fn
+       * @return {String}
+       * @api public
+       */
+      updatePassword: function(fn) {
+        // Handle new/update passwords
+        if (this.password) {
+          if (!validatePresenceOf(this.password)<% if (filters.oauth) { %> && authTypes.indexOf(this.provider) === -1<% } %>) {
+            fn(new Error('Invalid password'));
+          }
+
+          // Make salt with a callback
+          var _this = this;
+          this.makeSalt(function(saltErr, salt) {
+            if (saltErr) {
+              fn(saltErr);
+            }
+            _this.salt = salt;
+            _this.encryptPassword(_this.password, function(encryptErr, hashedPassword) {
+              if (encryptErr) {
+                fn(encryptErr);
+              }
+              _this.password = hashedPassword;
+              fn(null);
+            });
+          });
+        } else {
+          fn(null);
+        }
+      }
+    }
+  });
+
+  return User;
+};
diff --git a/app/templates/server/api/user(auth)/user.model.js b/app/templates/server/api/user(auth)/user.model.js
deleted file mode 100644
index cc8d59263..000000000
--- a/app/templates/server/api/user(auth)/user.model.js
+++ /dev/null
@@ -1,149 +0,0 @@
-'use strict';
-
-var mongoose = require('mongoose');
-var Schema = mongoose.Schema;
-var crypto = require('crypto');<% if(filters.oauth) { %>
-var authTypes = ['github', 'twitter', 'facebook', 'google'];<% } %>
-
-var UserSchema = new Schema({
-  name: String,
-  email: { type: String, lowercase: true },
-  role: {
-    type: String,
-    default: 'user'
-  },
-  hashedPassword: String,
-  provider: String,
-  salt: String<% if (filters.oauth) { %>,<% if (filters.facebookAuth) { %>
-  facebook: {},<% } %><% if (filters.twitterAuth) { %>
-  twitter: {},<% } %><% if (filters.googleAuth) { %>
-  google: {},<% } %>
-  github: {}<% } %>
-});
-
-/**
- * Virtuals
- */
-UserSchema
-  .virtual('password')
-  .set(function(password) {
-    this._password = password;
-    this.salt = this.makeSalt();
-    this.hashedPassword = this.encryptPassword(password);
-  })
-  .get(function() {
-    return this._password;
-  });
-
-// Public profile information
-UserSchema
-  .virtual('profile')
-  .get(function() {
-    return {
-      'name': this.name,
-      'role': this.role
-    };
-  });
-
-// Non-sensitive info we'll be putting in the token
-UserSchema
-  .virtual('token')
-  .get(function() {
-    return {
-      '_id': this._id,
-      'role': this.role
-    };
-  });
-
-/**
- * Validations
- */
-
-// Validate empty email
-UserSchema
-  .path('email')
-  .validate(function(email) {<% if (filters.oauth) { %>
-    if (authTypes.indexOf(this.provider) !== -1) return true;<% } %>
-    return email.length;
-  }, 'Email cannot be blank');
-
-// Validate empty password
-UserSchema
-  .path('hashedPassword')
-  .validate(function(hashedPassword) {<% if (filters.oauth) { %>
-    if (authTypes.indexOf(this.provider) !== -1) return true;<% } %>
-    return hashedPassword.length;
-  }, 'Password cannot be blank');
-
-// Validate email is not taken
-UserSchema
-  .path('email')
-  .validate(function(value, respond) {
-    var self = this;
-    this.constructor.findOne({email: value}, function(err, user) {
-      if(err) throw err;
-      if(user) {
-        if(self.id === user.id) return respond(true);
-        return respond(false);
-      }
-      respond(true);
-    });
-}, 'The specified email address is already in use.');
-
-var validatePresenceOf = function(value) {
-  return value && value.length;
-};
-
-/**
- * Pre-save hook
- */
-UserSchema
-  .pre('save', function(next) {
-    if (!this.isNew) return next();
-
-    if (!validatePresenceOf(this.hashedPassword)<% if (filters.oauth) { %> && authTypes.indexOf(this.provider) === -1<% } %>)
-      next(new Error('Invalid password'));
-    else
-      next();
-  });
-
-/**
- * Methods
- */
-UserSchema.methods = {
-  /**
-   * Authenticate - check if the passwords are the same
-   *
-   * @param {String} plainText
-   * @return {Boolean}
-   * @api public
-   */
-  authenticate: function(plainText) {
-    return this.encryptPassword(plainText) === this.hashedPassword;
-  },
-
-  /**
-   * Make salt
-   *
-   * @return {String}
-   * @api public
-   */
-  makeSalt: function() {
-    return crypto.randomBytes(16).toString('base64');
-  },
-
-  /**
-   * Encrypt password
-   *
-   * @param {String} password
-   * @return {String}
-   * @api public
-   */
-  encryptPassword: function(password) {
-    if (!password || !this.salt) return '';
-    var salt = new Buffer(this.salt, 'base64');
-    return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
-  }
-};
-
-module.exports = mongoose.model('User', UserSchema);
diff --git a/app/templates/server/api/user(auth)/user.model.spec(mongooseModels).js b/app/templates/server/api/user(auth)/user.model.spec(mongooseModels).js
new file mode 100644
index 000000000..099e4d5c6
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.model.spec(mongooseModels).js
@@ -0,0 +1,72 @@
+'use strict';
+
+var app = require('../..');
+var User = require('./user.model');
+var user;
+var genUser = function() {
+  user = new User({
+    provider: 'local',
+    name: 'Fake User',
+    email: 'test@test.com',
+    password: 'password'
+  });
+  return user;
+};
+
+describe('User Model', function() {
+  before(function() {
+    // Clear users before testing
+    return User.removeAsync();
+  });
+
+  beforeEach(function() {
+    genUser();
+  });
+
+  afterEach(function() {
+    return User.removeAsync();
+  });
+
+  it('should begin with no users', function() {
+    return User.findAsync({})
+      .should.eventually.have.length(0);
+  });
+
+  it('should fail when saving a duplicate user', function() {
+    return user.saveAsync()
+      .then(function() {
+        var userDup = genUser();
+        return userDup.saveAsync();
+      }).should.be.rejected;
+  });
+
+  describe('#email', function() {
+    it('should fail when saving without an email', function() {
+      user.email = '';
+      return user.saveAsync().should.be.rejected;
+    });
+  });
+
+  describe('#password', function() {
+    beforeEach(function() {
+      return user.saveAsync();
+    });
+
+    it('should authenticate user if valid', function() {
+      user.authenticate('password').should.be.true;
+    });
+
+    it('should not authenticate user if invalid', function() {
+      user.authenticate('blah').should.not.be.true;
+    });
+
+    it('should remain the same hash unless the password is updated', function() {
+      user.name = 'Test User';
+      return user.saveAsync()
+        .spread(function(u) {
+          return u.authenticate('password');
+        }).should.eventually.be.true;
+    });
+  });
+
+});
diff --git a/app/templates/server/api/user(auth)/user.model.spec(sequelizeModels).js b/app/templates/server/api/user(auth)/user.model.spec(sequelizeModels).js
new file mode 100644
index 000000000..7e0ca0cc4
--- /dev/null
+++ b/app/templates/server/api/user(auth)/user.model.spec(sequelizeModels).js
@@ -0,0 +1,74 @@
+'use strict';
+
+var app = require('../..');
+var User = require('../../sqldb').User;
+var user;
+var genUser = function() {
+  user = User.build({
+    provider: 'local',
+    name: 'Fake User',
+    email: 'test@test.com',
+    password: 'password'
+  });
+  return user;
+};
+
+describe('User Model', function() {
+  before(function() {
+    // Sync and clear users before testing
+    return User.sync().then(function() {
+      return User.destroy({ where: {} });
+    });
+  });
+
+  beforeEach(function() {
+    genUser();
+  });
+
+  afterEach(function() {
+    return User.destroy({ where: {} });
+  });
+
+  it('should begin with no users', function() {
+    return User.findAll()
+      .should.eventually.have.length(0);
+  });
+
+  it('should fail when saving a duplicate user', function() {
+    return user.save()
+      .then(function() {
+        var userDup = genUser();
+        return userDup.save();
+      }).should.be.rejected;
+  });
+
+  describe('#email', function() {
+    it('should fail when saving without an email', function() {
+      user.email = '';
+      return user.save().should.be.rejected;
+    });
+  });
+
+  describe('#password', function() {
+    beforeEach(function() {
+      return user.save();
+    });
+
+    it('should authenticate user if valid', function() {
+      user.authenticate('password').should.be.true;
+    });
+
+    it('should not authenticate user if invalid', function() {
+      user.authenticate('blah').should.not.be.true;
+    });
+
+    it('should remain the same hash unless the password is updated', function() {
+      user.name = 'Test User';
+      return user.save()
+        .then(function(u) {
+          return u.authenticate('password');
+        }).should.eventually.be.true;
+    });
+  });
+
+});
diff --git a/app/templates/server/api/user(auth)/user.model.spec.js b/app/templates/server/api/user(auth)/user.model.spec.js
deleted file mode 100644
index 257c95b7c..000000000
--- a/app/templates/server/api/user(auth)/user.model.spec.js
+++ /dev/null
@@ -1,60 +0,0 @@
-'use strict';
-
-var should = require('should');
-var app = require('../../app');
-var User = require('./user.model');
-
-var user = new User({
-  provider: 'local',
-  name: 'Fake User',
-  email: 'test@test.com',
-  password: 'password'
-});
-
-describe('User Model', function() {
-  before(function(done) {
-    // Clear users before testing
-    User.remove().exec().then(function() {
-      done();
-    });
-  });
-
-  afterEach(function(done) {
-    User.remove().exec().then(function() {
-      done();
-    });
-  });
-
-  it('should begin with no users', function(done) {
-    User.find({}, function(err, users) {
-      users.should.have.length(0);
-      done();
-    });
-  });
-
-  it('should fail when saving a duplicate user', function(done) {
-    user.save(function() {
-      var userDup = new User(user);
-      userDup.save(function(err) {
-        should.exist(err);
-        done();
-      });
-    });
-  });
-
-  it('should fail when saving without an email', function(done) {
-    user.email = '';
-    user.save(function(err) {
-      should.exist(err);
-      done();
-    });
-  });
-
-  it("should authenticate user if password is valid", function() {
-    return user.authenticate('password').should.be.true;
-  });
-
-  it("should not authenticate user if password is invalid", function() {
-    return user.authenticate('blah').should.not.be.true;
-  });
-});
diff --git a/app/templates/server/app.js b/app/templates/server/app.js
index f677d7a43..ca1e4ca78 100644
--- a/app/templates/server/app.js
+++ b/app/templates/server/app.js
@@ -8,20 +8,21 @@
 process.env.NODE_ENV = process.env.NODE_ENV || 'development';
 
 var express = require('express');<% if (filters.mongoose) { %>
-var mongoose = require('mongoose');<% } %>
+var mongoose = require('mongoose');<% } %><% if (filters.sequelize) { %>
+var sqldb = require('./sqldb');<% } %>
 var config = require('./config/environment');
 <% if (filters.mongoose) { %>
-// Connect to database
+// Connect to MongoDB
 mongoose.connect(config.mongo.uri, config.mongo.options);
 mongoose.connection.on('error', function(err) {
-	console.error('MongoDB connection error: ' + err);
-	process.exit(-1);
-	}
-);
-// Populate DB with sample data
-if(config.seedDB) { require('./config/seed'); }
-
-<% } %>// Setup server
+  console.error('MongoDB connection error: ' + err);
+  process.exit(-1);
+});
+<% } %><% if (filters.models) { %>
+// Populate databases with sample data
+if (config.seedDB) { require('./config/seed'); }
+<% } %>
+// Setup server
 var app = express();
 var server = require('http').createServer(app);<% if (filters.socketio) { %>
 var socketio = require('socket.io')(server, {
@@ -33,9 +34,19 @@ require('./config/express')(app);
 require('./routes')(app);
 
 // Start server
-server.listen(config.port, config.ip, function () {
-  console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
-});
-
+function startServer() {
+  server.listen(config.port, config.ip, function() {
+    console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
+  });
+}
+<% if (filters.sequelize) { %>
+sqldb.sequelize.sync()
+  .then(startServer)
+  .catch(function(err) {
+    console.log('Server failed to start due to error: %s', err);
+  });
+<% } else { %>
+setImmediate(startServer);
+<% } %>
 // Expose app
 exports = module.exports = app;
diff --git a/app/templates/server/auth(auth)/auth.service.js b/app/templates/server/auth(auth)/auth.service.js
index 370dac51e..8c100e84b 100644
--- a/app/templates/server/auth(auth)/auth.service.js
+++ b/app/templates/server/auth(auth)/auth.service.js
@@ -1,13 +1,15 @@
 'use strict';
 
-var mongoose = require('mongoose');
 var passport = require('passport');
 var config = require('../config/environment');
 var jwt = require('jsonwebtoken');
 var expressJwt = require('express-jwt');
-var compose = require('composable-middleware');
-var User = require('../api/user/user.model');
-var validateJwt = expressJwt({ secret: config.secrets.session });
+var compose = require('composable-middleware');<% if (filters.mongooseModels) { %>
+var User = require('../api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var User = require('../sqldb').User;<% } %>
+var validateJwt = expressJwt({
+  secret: config.secrets.session
+});
 
 /**
  * Attaches the user object to the request if authenticated
@@ -18,20 +20,29 @@ function isAuthenticated() {
     // Validate jwt
     .use(function(req, res, next) {
       // allow access_token to be passed through query parameter as well
-      if(req.query && req.query.hasOwnProperty('access_token')) {
+      if (req.query && req.query.hasOwnProperty('access_token')) {
         req.headers.authorization = 'Bearer ' + req.query.access_token;
       }
       validateJwt(req, res, next);
     })
     // Attach user to request
     .use(function(req, res, next) {
-      User.findById(req.user._id, function (err, user) {
-        if (err) return next(err);
-        if (!user) return res.status(401).send('Unauthorized');
-
-        req.user = user;
-        next();
-      });
+      <% if (filters.mongooseModels) { %>User.findByIdAsync(req.user._id)<% }
+         if (filters.sequelizeModels) { %>User.find({
+        where: {
+          _id: req.user._id
+        }
+      })<% } %>
+        .then(function(user) {
+          if (!user) {
+            return res.status(401).end();
+          }
+          req.user = user;
+          next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
     });
 }
 
@@ -39,12 +50,15 @@ function isAuthenticated() {
  * Checks if the user role meets the minimum requirements of the route
  */
 function hasRole(roleRequired) {
-  if (!roleRequired) throw new Error('Required role needs to be set');
+  if (!roleRequired) {
+    throw new Error('Required role needs to be set');
+  }
 
   return compose()
     .use(isAuthenticated())
     .use(function meetsRequirements(req, res, next) {
-      if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) {
+      if (config.userRoles.indexOf(req.user.role) >=
+          config.userRoles.indexOf(roleRequired)) {
         next();
       }
       else {
@@ -56,15 +70,19 @@ function hasRole(roleRequired) {
 /**
  * Returns a jwt token signed by the app secret
  */
-function signToken(id) {
-  return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 });
+function signToken(id, role) {
+  return jwt.sign({ _id: id, role: role }, config.secrets.session, {
+    expiresInMinutes: 60 * 5
+  });
 }
 
 /**
  * Set token cookie directly for oAuth strategies
  */
 function setTokenCookie(req, res) {
-  if (!req.user) return res.status(404).json({ message: 'Something went wrong, please try again.'});
+  if (!req.user) {
+    return res.status(404).send('Something went wrong, please try again.');
+  }
   var token = signToken(req.user._id, req.user.role);
   res.cookie('token', JSON.stringify(token));
   res.redirect('/');
@@ -73,4 +91,4 @@ function setTokenCookie(req, res) {
 exports.isAuthenticated = isAuthenticated;
 exports.hasRole = hasRole;
 exports.signToken = signToken;
-exports.setTokenCookie = setTokenCookie;
\ No newline at end of file
+exports.setTokenCookie = setTokenCookie;
diff --git a/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js b/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js
index 4a6f87886..f13d463e1 100644
--- a/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js
+++ b/app/templates/server/auth(auth)/facebook(facebookAuth)/index.js
@@ -18,4 +18,4 @@ router
     session: false
   }), auth.setTokenCookie);
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js b/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js
index 54574efb6..00b87a226 100644
--- a/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js
+++ b/app/templates/server/auth(auth)/facebook(facebookAuth)/passport.js
@@ -1,37 +1,45 @@
 var passport = require('passport');
 var FacebookStrategy = require('passport-facebook').Strategy;
 
-exports.setup = function (User, config) {
+exports.setup = function(User, config) {
   passport.use(new FacebookStrategy({
-      clientID: config.facebook.clientID,
-      clientSecret: config.facebook.clientSecret,
-      callbackURL: config.facebook.callbackURL
-    },
-    function(accessToken, refreshToken, profile, done) {
-      User.findOne({
-        'facebook.id': profile.id
-      },
-      function(err, user) {
-        if (err) {
-          return done(err);
-        }
+    clientID: config.facebook.clientID,
+    clientSecret: config.facebook.clientSecret,
+    callbackURL: config.facebook.callbackURL,
+    profileFields: [
+      'displayName',
+      'emails'
+    ]
+  },
+  function(accessToken, refreshToken, profile, done) {
+    <% if (filters.mongooseModels) { %>User.findOneAsync({<% }
+       if (filters.sequelizeModels) { %>User.find({<% } %>
+      'facebook.id': profile.id
+    })
+      .then(function(user) {
         if (!user) {
-          user = new User({
+          <% if (filters.mongooseModels) { %>user = new User({<% }
+             if (filters.sequelizeModels) { %>user = User.build({<% } %>
             name: profile.displayName,
             email: profile.emails[0].value,
             role: 'user',
-            username: profile.username,
             provider: 'facebook',
             facebook: profile._json
           });
-          user.save(function(err) {
-            if (err) return done(err);
-            done(err, user);
-          });
+          <% if (filters.mongooseModels) { %>user.saveAsync()<% }
+             if (filters.sequelizeModels) { %>user.save()<% } %>
+            .then(function(user) {
+              return done(null, user);
+            })
+            .catch(function(err) {
+              return done(err);
+            });
         } else {
-          return done(err, user);
+          return done(null, user);
         }
       })
-    }
-  ));
+      .catch(function(err) {
+        return done(err);
+      });
+  }));
 };
diff --git a/app/templates/server/auth(auth)/google(googleAuth)/index.js b/app/templates/server/auth(auth)/google(googleAuth)/index.js
index 9b1ce39fe..7789def92 100644
--- a/app/templates/server/auth(auth)/google(googleAuth)/index.js
+++ b/app/templates/server/auth(auth)/google(googleAuth)/index.js
@@ -10,8 +10,8 @@ router
   .get('/', passport.authenticate('google', {
     failureRedirect: '/signup',
     scope: [
-      'https://www.googleapis.com/auth/userinfo.profile',
-      'https://www.googleapis.com/auth/userinfo.email'
+      'profile',
+      'email'
     ],
     session: false
   }))
@@ -21,4 +21,4 @@ router
     session: false
   }), auth.setTokenCookie);
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/templates/server/auth(auth)/google(googleAuth)/passport.js b/app/templates/server/auth(auth)/google(googleAuth)/passport.js
index c9754c83a..f74594c12 100644
--- a/app/templates/server/auth(auth)/google(googleAuth)/passport.js
+++ b/app/templates/server/auth(auth)/google(googleAuth)/passport.js
@@ -1,33 +1,42 @@
 var passport = require('passport');
 var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
 
-exports.setup = function (User, config) {
+exports.setup = function(User, config) {
   passport.use(new GoogleStrategy({
-      clientID: config.google.clientID,
-      clientSecret: config.google.clientSecret,
-      callbackURL: config.google.callbackURL
-    },
-    function(accessToken, refreshToken, profile, done) {
-      User.findOne({
-        'google.id': profile.id
-      }, function(err, user) {
+    clientID: config.google.clientID,
+    clientSecret: config.google.clientSecret,
+    callbackURL: config.google.callbackURL
+  },
+  function(accessToken, refreshToken, profile, done) {
+    <% if (filters.mongooseModels) { %>User.findOneAsync({<% }
+       if (filters.sequelizeModels) { %>User.find({<% } %>
+      'google.id': profile.id
+    })
+      .then(function(user) {
         if (!user) {
-          user = new User({
+          <% if (filters.mongooseModels) { %>user = new User({<% }
+             if (filters.sequelizeModels) { %>user = User.build({<% } %>
             name: profile.displayName,
             email: profile.emails[0].value,
             role: 'user',
-            username: profile.username,
+            username: profile.emails[0].value.split('@')[0],
             provider: 'google',
             google: profile._json
           });
-          user.save(function(err) {
-            if (err) return done(err);
-            done(err, user);
-          });
+          <% if (filters.mongooseModels) { %>user.saveAsync()<% }
+             if (filters.sequelizeModels) { %>user.save()<% } %>
+            .then(function(user) {
+              return done(null, user);
+            })
+            .catch(function(err) {
+              return done(err);
+            });
         } else {
-          return done(err, user);
+          return done(null, user);
         }
+      })
+      .catch(function(err) {
+        return done(err);
       });
-    }
-  ));
+  }));
 };
diff --git a/app/templates/server/auth(auth)/index.js b/app/templates/server/auth(auth)/index.js
index e3e6c87ad..75ddfdcb8 100644
--- a/app/templates/server/auth(auth)/index.js
+++ b/app/templates/server/auth(auth)/index.js
@@ -2,8 +2,9 @@
 
 var express = require('express');
 var passport = require('passport');
-var config = require('../config/environment');
-var User = require('../api/user/user.model');
+var config = require('../config/environment');<% if (filters.mongooseModels) { %>
+var User = require('../api/user/user.model');<% } %><% if (filters.sequelizeModels) { %>
+var User = require('../sqldb').User;<% } %>
 
 // Passport Configuration
 require('./local/passport').setup(User, config);<% if (filters.facebookAuth) { %>
@@ -18,4 +19,4 @@ router.use('/facebook', require('./facebook'));<% } %><% if (filters.twitterAuth
 router.use('/twitter', require('./twitter'));<% } %><% if (filters.googleAuth) { %>
 router.use('/google', require('./google'));<% } %>
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/templates/server/auth(auth)/local/index.js b/app/templates/server/auth(auth)/local/index.js
index 2e761a52d..e94d0da1a 100644
--- a/app/templates/server/auth(auth)/local/index.js
+++ b/app/templates/server/auth(auth)/local/index.js
@@ -7,14 +7,18 @@ var auth = require('../auth.service');
 var router = express.Router();
 
 router.post('/', function(req, res, next) {
-  passport.authenticate('local', function (err, user, info) {
+  passport.authenticate('local', function(err, user, info) {
     var error = err || info;
-    if (error) return res.status(401).json(error);
-    if (!user) return res.status(404).json({message: 'Something went wrong, please try again.'});
+    if (error) {
+      return res.status(401).json(error);
+    }
+    if (!user) {
+      return res.status(404).json({message: 'Something went wrong, please try again.'});
+    }
 
     var token = auth.signToken(user._id, user.role);
-    res.json({token: token});
+    res.json({ token: token });
   })(req, res, next)
 });
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/templates/server/auth(auth)/local/passport.js b/app/templates/server/auth(auth)/local/passport.js
index ac82b42a2..2bd3366f8 100644
--- a/app/templates/server/auth(auth)/local/passport.js
+++ b/app/templates/server/auth(auth)/local/passport.js
@@ -1,25 +1,44 @@
 var passport = require('passport');
 var LocalStrategy = require('passport-local').Strategy;
 
-exports.setup = function (User, config) {
-  passport.use(new LocalStrategy({
-      usernameField: 'email',
-      passwordField: 'password' // this is the virtual field on the model
-    },
-    function(email, password, done) {
-      User.findOne({
-        email: email.toLowerCase()
-      }, function(err, user) {
-        if (err) return done(err);
-
-        if (!user) {
-          return done(null, false, { message: 'This email is not registered.' });
+function localAuthenticate(User, email, password, done) {
+  <% if (filters.mongooseModels) { %>User.findOneAsync({
+    email: email.toLowerCase()
+  })<% }
+     if (filters.sequelizeModels) { %>User.find({
+    where: {
+      email: email.toLowerCase()
+    }
+  })<% } %>
+    .then(function(user) {
+      if (!user) {
+        return done(null, false, {
+          message: 'This email is not registered.'
+        });
+      }
+      user.authenticate(password, function(authError, authenticated) {
+        if (authError) {
+          return done(authError);
         }
-        if (!user.authenticate(password)) {
-          return done(null, false, { message: 'This password is not correct.' });
+        if (!authenticated) {
+          return done(null, false, {
+            message: 'This password is not correct.'
+          });
+        } else {
+          return done(null, user);
         }
-        return done(null, user);
       });
-    }
-  ));
-};
\ No newline at end of file
+    })
+    .catch(function(err) {
+      return done(err);
+    });
+}
+
+exports.setup = function(User, config) {
+  passport.use(new LocalStrategy({
+    usernameField: 'email',
+    passwordField: 'password' // this is the virtual field on the model
+  }, function(email, password, done) {<% if (filters.models) { %>
+    return localAuthenticate(User, email, password, done);
+<% } %>  }));
+};
diff --git a/app/templates/server/auth(auth)/twitter(twitterAuth)/index.js b/app/templates/server/auth(auth)/twitter(twitterAuth)/index.js
index 8360247b8..8e6f32b5d 100644
--- a/app/templates/server/auth(auth)/twitter(twitterAuth)/index.js
+++ b/app/templates/server/auth(auth)/twitter(twitterAuth)/index.js
@@ -17,4 +17,4 @@ router
     session: false
   }), auth.setTokenCookie);
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js b/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js
index 4544ce186..bf23bd3ba 100644
--- a/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js
+++ b/app/templates/server/auth(auth)/twitter(twitterAuth)/passport.js
@@ -1,4 +1,4 @@
-exports.setup = function (User, config) {
+exports.setup = function(User, config) {
   var passport = require('passport');
   var TwitterStrategy = require('passport-twitter').Strategy;
 
@@ -8,28 +8,34 @@ exports.setup = function (User, config) {
     callbackURL: config.twitter.callbackURL
   },
   function(token, tokenSecret, profile, done) {
-    User.findOne({
+    <% if (filters.mongooseModels) { %>User.findOneAsync({<% }
+       if (filters.sequelizeModels) { %>User.find({<% } %>
       'twitter.id_str': profile.id
-    }, function(err, user) {
-      if (err) {
+    })
+      .then(function(user) {
+        if (!user) {
+          <% if (filters.mongooseModels) { %>user = new User({<% }
+             if (filters.sequelizeModels) { %>user = User.build({<% } %>
+            name: profile.displayName,
+            username: profile.username,
+            role: 'user',
+            provider: 'twitter',
+            twitter: profile._json
+          });
+          <% if (filters.mongooseModels) { %>user.saveAsync()<% }
+             if (filters.sequelizeModels) { %>user.save()<% } %>
+            .then(function(user) {
+              return done(null, user);
+            })
+            .catch(function(err) {
+              return done(err);
+            });
+        } else {
+          return done(null, user);
+        }
+      })
+      .catch(function(err) {
         return done(err);
-      }
-      if (!user) {
-        user = new User({
-          name: profile.displayName,
-          username: profile.username,
-          role: 'user',
-          provider: 'twitter',
-          twitter: profile._json
-        });
-        user.save(function(err) {
-          if (err) return done(err);
-          done(err, user);
-        });
-      } else {
-        return done(err, user);
-      }
-    });
-    }
-  ));
+      });
+  }));
 };
diff --git a/app/templates/server/components/errors/index.js b/app/templates/server/components/errors/index.js
index 4c5a57c99..ba71c73ba 100644
--- a/app/templates/server/components/errors/index.js
+++ b/app/templates/server/components/errors/index.js
@@ -12,9 +12,11 @@ module.exports[404] = function pageNotFound(req, res) {
   };
 
   res.status(result.status);
-  res.render(viewFilePath, function (err) {
-    if (err) { return res.json(result, result.status); }
+  res.render(viewFilePath, {}, function(err, html) {
+    if (err) {
+      return res.json(result, result.status);
+    }
 
-    res.render(viewFilePath);
+    res.send(html);
   });
 };
diff --git a/app/templates/server/config/_local.env.js b/app/templates/server/config/_local.env.js
index c24fffd3a..12b78192e 100644
--- a/app/templates/server/config/_local.env.js
+++ b/app/templates/server/config/_local.env.js
@@ -7,7 +7,7 @@
 
 module.exports = {
   DOMAIN: 'http://localhost:9000',
-  SESSION_SECRET: "<%= _.slugify(appname) + '-secret' %>",<% if (filters.facebookAuth) { %>
+  SESSION_SECRET: '<%= _.slugify(appname) + "-secret" %>',<% if (filters.facebookAuth) { %>
 
   FACEBOOK_ID: 'app-id',
   FACEBOOK_SECRET: 'secret',<% } if (filters.twitterAuth) { %>
diff --git a/app/templates/server/config/environment/development.js b/app/templates/server/config/environment/development.js
index fb33d6eab..20656595b 100644
--- a/app/templates/server/config/environment/development.js
+++ b/app/templates/server/config/environment/development.js
@@ -7,6 +7,16 @@ module.exports = {
   mongo: {
     uri: 'mongodb://localhost/<%= _.slugify(appname) %>-dev'
   },
+  sequelize: {
+    uri: 'sqlite://',
+    options: {
+      logging: false,
+      storage: 'dev.sqlite',
+      define: {
+        timestamps: false
+      }
+    }
+  },
 
   seedDB: true
 };
diff --git a/app/templates/server/config/environment/index.js b/app/templates/server/config/environment/index.js
index a57261ddc..547f6bbac 100644
--- a/app/templates/server/config/environment/index.js
+++ b/app/templates/server/config/environment/index.js
@@ -4,7 +4,7 @@ var path = require('path');
 var _ = require('lodash');
 
 function requiredProcessEnv(name) {
-  if(!process.env[name]) {
+  if (!process.env[name]) {
     throw new Error('You must set the ' + name + ' environment variable');
   }
   return process.env[name];
@@ -42,20 +42,20 @@ var all = {
         safe: true
       }
     }
-  },
-<% if(filters.facebookAuth) { %>
+  }<% if (filters.facebookAuth) { %>,
+
   facebook: {
     clientID:     process.env.FACEBOOK_ID || 'id',
     clientSecret: process.env.FACEBOOK_SECRET || 'secret',
     callbackURL:  (process.env.DOMAIN || '') + '/auth/facebook/callback'
-  },
-<% } %><% if(filters.twitterAuth) { %>
+  }<% } %><% if (filters.twitterAuth) { %>,
+
   twitter: {
     clientID:     process.env.TWITTER_ID || 'id',
     clientSecret: process.env.TWITTER_SECRET || 'secret',
     callbackURL:  (process.env.DOMAIN || '') + '/auth/twitter/callback'
-  },
-<% } %><% if(filters.googleAuth) { %>
+  }<% } %><% if (filters.googleAuth) { %>,
+
   google: {
     clientID:     process.env.GOOGLE_ID || 'id',
     clientSecret: process.env.GOOGLE_SECRET || 'secret',
diff --git a/app/templates/server/config/environment/production.js b/app/templates/server/config/environment/production.js
index 1704df619..e0b77bf97 100644
--- a/app/templates/server/config/environment/production.js
+++ b/app/templates/server/config/environment/production.js
@@ -17,7 +17,8 @@ module.exports = {
   mongo: {
     uri:    process.env.MONGOLAB_URI ||
             process.env.MONGOHQ_URL ||
-            process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME ||
+            process.env.OPENSHIFT_MONGODB_DB_URL +
+            process.env.OPENSHIFT_APP_NAME ||
             'mongodb://localhost/<%= _.slugify(appname) %>'
   }
-};
\ No newline at end of file
+};
diff --git a/app/templates/server/config/environment/test.js b/app/templates/server/config/environment/test.js
index 711c98660..021938424 100644
--- a/app/templates/server/config/environment/test.js
+++ b/app/templates/server/config/environment/test.js
@@ -6,5 +6,15 @@ module.exports = {
   // MongoDB connection options
   mongo: {
     uri: 'mongodb://localhost/<%= _.slugify(appname) %>-test'
+  },
+  sequelize: {
+    uri: 'sqlite://',
+    options: {
+      logging: false,
+      storage: 'test.sqlite',
+      define: {
+        timestamps: false
+      }
+    }
   }
-};
\ No newline at end of file
+};
diff --git a/app/templates/server/config/express.js b/app/templates/server/config/express.js
index f04098387..aa32be65a 100644
--- a/app/templates/server/config/express.js
+++ b/app/templates/server/config/express.js
@@ -15,9 +15,11 @@ var errorHandler = require('errorhandler');
 var path = require('path');
 var config = require('./environment');<% if (filters.auth) { %>
 var passport = require('passport');<% } %><% if (filters.twitterAuth) { %>
-var session = require('express-session');
+var session = require('express-session');<% if (filters.mongoose) { %>
 var mongoStore = require('connect-mongo')(session);
-var mongoose = require('mongoose');<% } %>
+var mongoose = require('mongoose');<% } else if(filters.sequelize) { %>
+var sqldb = require('../sqldb');
+var Store = require('express-sequelize-session')(session.Store);<% } %><% } %>
 
 module.exports = function(app) {
   var env = app.get('env');
@@ -30,34 +32,38 @@ module.exports = function(app) {
   app.use(bodyParser.urlencoded({ extended: false }));
   app.use(bodyParser.json());
   app.use(methodOverride());
-  app.use(cookieParser());
-  <% if (filters.auth) { %>app.use(passport.initialize());<% } %><% if (filters.twitterAuth) { %>
+  app.use(cookieParser());<% if (filters.auth) { %>
+  app.use(passport.initialize());<% } %><% if (filters.twitterAuth) { %>
 
-  // Persist sessions with mongoStore
+  // Persist sessions with mongoStore / sequelizeStore
   // We need to enable sessions for passport twitter because its an oauth 1.0 strategy
   app.use(session({
     secret: config.secrets.session,
     resave: true,
-    saveUninitialized: true,
+    saveUninitialized: true<% if (filters.mongoose) { %>,
     store: new mongoStore({
       mongooseConnection: mongoose.connection,
       db: '<%= _.slugify(_.humanize(appname)) %>'
-    })
+    })<% } else if(filters.sequelize) { %>,
+    store: new Store(sqldb.sequelize)<% } %>
   }));
-  <% } %>
+<% } %>
+  app.set('appPath', path.join(config.root, 'client'));
+
   if ('production' === env) {
-    app.use(favicon(path.join(config.root, 'public', 'favicon.ico')));
-    app.use(express.static(path.join(config.root, 'public')));
-    app.set('appPath', path.join(config.root, 'public'));
+    app.use(favicon(path.join(config.root, 'client', 'favicon.ico')));
+    app.use(express.static(app.get('appPath')));
     app.use(morgan('dev'));
   }
 
-  if ('development' === env || 'test' === env) {
+  if ('development' === env) {
     app.use(require('connect-livereload')());
+  }
+
+  if ('development' === env || 'test' === env) {
     app.use(express.static(path.join(config.root, '.tmp')));
-    app.use(express.static(path.join(config.root, 'client')));
-    app.set('appPath', path.join(config.root, 'client'));
+    app.use(express.static(app.get('appPath')));
     app.use(morgan('dev'));
     app.use(errorHandler()); // Error handler - has to be last
   }
-};
\ No newline at end of file
+};
diff --git a/app/templates/server/config/seed(models).js b/app/templates/server/config/seed(models).js
new file mode 100644
index 000000000..20ba6b0f2
--- /dev/null
+++ b/app/templates/server/config/seed(models).js
@@ -0,0 +1,76 @@
+/**
+ * Populate DB with sample data on server start
+ * to disable, edit config/environment/index.js, and set `seedDB: false`
+ */
+
+'use strict';
+<% if (filters.mongooseModels) { %>
+var Thing = require('../api/thing/thing.model');
+<% if (filters.auth) { %>var User = require('../api/user/user.model');<% } %>
+<% } %><% if (filters.sequelizeModels) { %>
+var sqldb = require('../sqldb');
+var Thing = sqldb.Thing;
+<% if (filters.auth) { %>var User = sqldb.User;<% } %>
+<% } %>
+<% if (filters.mongooseModels) { %>Thing.find({}).removeAsync()<% }
+   if (filters.sequelizeModels) { %>Thing.sync()
+  .then(function() {
+    return Thing.destroy({ where: {} });
+  })<% } %>
+  .then(function() {
+    <% if (filters.mongooseModels) { %>Thing.create({<% }
+       if (filters.sequelizeModels) { %>Thing.bulkCreate([{<% } %>
+      name: 'Development Tools',
+      info: 'Integration with popular tools such as Bower, Grunt, Karma, ' +
+             'Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, ' +
+             'Stylus, Sass, CoffeeScript, and Less.'
+    }, {
+      name: 'Server and Client integration',
+      info: 'Built with a powerful and fun stack: MongoDB, Express, ' +
+             'AngularJS, and Node.'
+    }, {
+      name: 'Smart Build System',
+      info: 'Build system ignores `spec` files, allowing you to keep ' +
+             'tests alongside code. Automatic injection of scripts and ' +
+             'styles into your index.html'
+    }, {
+      name: 'Modular Structure',
+      info: 'Best practice client and server structures allow for more ' +
+             'code reusability and maximum scalability'
+    }, {
+      name: 'Optimized Build',
+      info: 'Build process packs up your templates as a single JavaScript ' +
+             'payload, minifies your scripts/css/images, and rewrites asset ' +
+             'names for caching.'
+    }, {
+      name: 'Deployment Ready',
+      info: 'Easily deploy your app to Heroku or Openshift with the heroku ' +
+             'and openshift subgenerators'
+    <% if (filters.mongooseModels) { %>});<% }
+       if (filters.sequelizeModels) { %>}]);<% } %>
+  });
+<% if (filters.auth) { %>
+<% if (filters.mongooseModels) { %>User.find({}).removeAsync()<% }
+   if (filters.sequelizeModels) { %>User.sync()
+  .then(function() {
+    return User.destroy({ where: {} });
+  })<% } %>
+  .then(function() {
+    <% if (filters.mongooseModels) { %>User.createAsync({<% }
+       if (filters.sequelizeModels) { %>User.bulkCreate([{<% } %>
+      provider: 'local',
+      name: 'Test User',
+      email: 'test@test.com',
+      password: 'test'
+    }, {
+      provider: 'local',
+      role: 'admin',
+      name: 'Admin',
+      email: 'admin@admin.com',
+      password: 'admin'
+    <% if (filters.mongooseModels) { %>})<% }
+       if (filters.sequelizeModels) { %>}])<% } %>
+    .then(function() {
+      console.log('finished populating users');
+    });
+  });<% } %>
diff --git a/app/templates/server/config/seed(mongoose).js b/app/templates/server/config/seed(mongoose).js
deleted file mode 100644
index 27ab19417..000000000
--- a/app/templates/server/config/seed(mongoose).js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Populate DB with sample data on server start
- * to disable, edit config/environment/index.js, and set `seedDB: false`
- */
-
-'use strict';
-
-var Thing = require('../api/thing/thing.model');
-<% if (filters.auth) { %>var User = require('../api/user/user.model');<% } %>
-
-Thing.find({}).remove(function() {
-  Thing.create({
-    name : 'Development Tools',
-    info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.'
-  }, {
-    name : 'Server and Client integration',
-    info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.'
-  }, {
-    name : 'Smart Build System',
-    info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html'
-  },  {
-    name : 'Modular Structure',
-    info : 'Best practice client and server structures allow for more code reusability and maximum scalability'
-  },  {
-    name : 'Optimized Build',
-    info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.'
-  },{
-    name : 'Deployment Ready',
-    info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
-  });
-});<% if (filters.auth) { %>
-
-User.find({}).remove(function() {
-  User.create({
-    provider: 'local',
-    name: 'Test User',
-    email: 'test@test.com',
-    password: 'test'
-  }, {
-    provider: 'local',
-    role: 'admin',
-    name: 'Admin',
-    email: 'admin@admin.com',
-    password: 'admin'
-  }, function() {
-      console.log('finished populating users');
-    }
-  );
-});<% } %>
\ No newline at end of file
diff --git a/app/templates/server/config/socketio(socketio).js b/app/templates/server/config/socketio(socketio).js
index 2fbbc07d6..92f629729 100644
--- a/app/templates/server/config/socketio(socketio).js
+++ b/app/templates/server/config/socketio(socketio).js
@@ -13,7 +13,7 @@ function onDisconnect(socket) {
 // When the user connects.. perform this
 function onConnect(socket) {
   // When the client emits 'info', this listens and executes
-  socket.on('info', function (data) {
+  socket.on('info', function(data) {
     console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2));
   });
 
@@ -21,13 +21,13 @@ function onConnect(socket) {
   require('../api/thing/thing.socket').register(socket);
 }
 
-module.exports = function (socketio) {
+module.exports = function(socketio) {
   // socket.io (v1.x.x) is powered by debug.
   // In order to see all the debug output, set DEBUG (in server/config/local.env.js) to including the desired scope.
   //
   // ex: DEBUG: "http*,socket.io:socket"
 
-  // We can authenticate socket.io users and access their token through socket.handshake.decoded_token
+  // We can authenticate socket.io users and access their token through socket.decoded_token
   //
   // 1. You will need to send the token in `client/components/socket/socket.service.js`
   //
@@ -37,15 +37,14 @@ module.exports = function (socketio) {
   //   handshake: true
   // }));
 
-  socketio.on('connection', function (socket) {
-    socket.address = socket.handshake.address !== null ?
-            socket.handshake.address.address + ':' + socket.handshake.address.port :
-            process.env.DOMAIN;
+  socketio.on('connection', function(socket) {
+    socket.address = socket.request.connection.remoteAddress +
+      ':' + socket.request.connection.remotePort;
 
     socket.connectedAt = new Date();
 
     // Call onDisconnect.
-    socket.on('disconnect', function () {
+    socket.on('disconnect', function() {
       onDisconnect(socket);
       console.info('[%s] DISCONNECTED', socket.address);
     });
@@ -54,4 +53,4 @@ module.exports = function (socketio) {
     onConnect(socket);
     console.info('[%s] CONNECTED', socket.address);
   });
-};
\ No newline at end of file
+};
diff --git a/app/templates/server/index.js b/app/templates/server/index.js
new file mode 100644
index 000000000..fc65cd5f4
--- /dev/null
+++ b/app/templates/server/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+// Register the Babel require hook
+require('babel-core/register');
+
+// Export the application
+exports = module.exports = require('./app');
diff --git a/app/templates/server/routes.js b/app/templates/server/routes.js
index ebcd79dc6..8330b35fe 100644
--- a/app/templates/server/routes.js
+++ b/app/templates/server/routes.js
@@ -10,11 +10,11 @@ var path = require('path');
 module.exports = function(app) {
 
   // Insert routes below
-  app.use('/api/things', require('./api/thing'));
-  <% if (filters.auth) { %>app.use('/api/users', require('./api/user'));
+  app.use('/api/things', require('./api/thing'));<% if (filters.auth) { %>
+  app.use('/api/users', require('./api/user'));
 
   app.use('/auth', require('./auth'));
-  <% } %>
+<% } %>
   // All undefined asset or api routes should return a 404
   app.route('/:url(api|auth|components|app|bower_components|assets)/*')
    .get(errors[404]);
diff --git a/app/templates/server/sqldb(sequelize)/index.js b/app/templates/server/sqldb(sequelize)/index.js
new file mode 100644
index 000000000..2500a2213
--- /dev/null
+++ b/app/templates/server/sqldb(sequelize)/index.js
@@ -0,0 +1,20 @@
+/**
+ * Sequelize initialization module
+ */
+
+'use strict';
+
+var path = require('path');
+var config = require('../config/environment');
+
+var Sequelize = require('sequelize');
+
+var db = {
+  Sequelize: Sequelize,
+  sequelize: new Sequelize(config.sequelize.uri, config.sequelize.options)
+};
+
+// Insert models below<% if (filters.sequelizeModels && filters.auth) { %>
+db.User = db.sequelize.import('../api/user/user.model');<% } %>
+
+module.exports = db;
diff --git a/controller/index.js b/controller/index.js
index 29f65325b..6d8897d61 100644
--- a/controller/index.js
+++ b/controller/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/decorator/index.js b/decorator/index.js
index b28be5c88..ae8193eb7 100644
--- a/decorator/index.js
+++ b/decorator/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/directive/index.js b/directive/index.js
index 298f4240e..257a4b19a 100644
--- a/directive/index.js
+++ b/directive/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/endpoint/index.js b/endpoint/index.js
index 2b3d7eb22..cbb8d5aeb 100644
--- a/endpoint/index.js
+++ b/endpoint/index.js
@@ -7,12 +7,59 @@ var ScriptBase = require('../script-base.js');
 
 var Generator = module.exports = function Generator() {
   ScriptBase.apply(this, arguments);
+
+  this.option('route', {
+    desc: 'URL for the endpoint',
+    type: String
+  });
+
+  this.option('models', {
+    desc: 'Specify which model(s) to use',
+    type: String
+  });
+
+  this.option('endpointDirectory', {
+    desc: 'Parent directory for enpoints',
+    type: String
+  });
 };
 
 util.inherits(Generator, ScriptBase);
 
-Generator.prototype.askFor = function askFor() {
+Generator.prototype.prompting = function askFor() {
   var done = this.async();
+  var promptCb = function (props) {
+    if(props.route.charAt(0) !== '/') {
+      props.route = '/' + props.route;
+    }
+
+    this.route = props.route;
+
+    if (props.models) {
+      delete this.filters.mongoose;
+      delete this.filters.mongooseModels;
+      delete this.filters.sequelize;
+      delete this.filters.sequelizeModels;
+
+      this.filters[props.models] = true;
+      this.filters[props.models + 'Models'] = true;
+    }
+    done();
+  }.bind(this);
+
+  if (this.options.route) {
+    if (this.filters.mongoose && this.filters.sequelize) {
+      if (this.options.models) {
+        return promptCb(this.options);
+      }
+    } else {
+      if (this.filters.mongooseModels) { this.options.models = 'mongoose'; }
+      else if (this.filters.sequelizeModels) { this.options.models = 'sequelize'; }
+      else { delete this.options.models; }
+      return promptCb(this.options);
+    }
+  }
+
   var name = this.name;
 
   var base = this.config.get('routesBase') || '/api/';
@@ -25,52 +72,79 @@ Generator.prototype.askFor = function askFor() {
     name = name + 's';
   }
 
+  var self = this;
   var prompts = [
     {
       name: 'route',
       message: 'What will the url of your endpoint be?',
       default: base + name
+    },
+    {
+      type: 'list',
+      name: 'models',
+      message: 'What would you like to use for the endpoint\'s models?',
+      choices: [ 'Mongoose', 'Sequelize' ],
+      filter: function( val ) {
+        return val.toLowerCase();
+      },
+      when: function() {
+        return self.filters.mongoose && self.filters.sequelize;
+      }
     }
   ];
 
-  this.prompt(prompts, function (props) {
-    if(props.route.charAt(0) !== '/') {
-      props.route = '/' + props.route;
-    }
+  this.prompt(prompts, promptCb);
+};
 
-    this.route = props.route;
-    done();
-  }.bind(this));
+Generator.prototype.configuring = function config() {
+  this.routeDest = path.join(this.options.endpointDirectory ||
+    this.config.get('endpointDirectory') || 'server/api/', this.name);
 };
 
-Generator.prototype.registerEndpoint = function registerEndpoint() {
+Generator.prototype.writing = function createFiles() {
+  this.sourceRoot(path.join(__dirname, './templates'));
+  ngUtil.processDirectory(this, '.', this.routeDest);
+};
+
+Generator.prototype.end = function registerEndpoint() {
   if(this.config.get('insertRoutes')) {
+    var routesFile = this.config.get('registerRoutesFile');
+    var reqPath = this.relativeRequire(this.routeDest, routesFile);
     var routeConfig = {
-      file: this.config.get('registerRoutesFile'),
+      file: routesFile,
       needle: this.config.get('routesNeedle'),
       splicable: [
-        "app.use(\'" + this.route +"\', require(\'./api/" + this.name + "\'));"
+        "app.use(\'" + this.route +"\', require(\'" + reqPath + "\'));"
       ]
     };
     ngUtil.rewriteFile(routeConfig);
   }
 
-  if (this.filters.socketio) {
-    if(this.config.get('insertSockets')) {
-      var socketConfig = {
-        file: this.config.get('registerSocketsFile'),
-        needle: this.config.get('socketsNeedle'),
-        splicable: [
-          "require(\'../api/" + this.name + '/' + this.name + ".socket\').register(socket);"
-        ]
-      };
-      ngUtil.rewriteFile(socketConfig);
-    }
+  if (this.filters.socketio && this.config.get('insertSockets')) {
+    var socketsFile = this.config.get('registerSocketsFile');
+    var reqPath = this.relativeRequire(this.routeDest + '/' + this.basename +
+      '.socket', socketsFile);
+    var socketConfig = {
+      file: socketsFile,
+      needle: this.config.get('socketsNeedle'),
+      splicable: [
+        "require(\'" + reqPath + "\').register(socket);"
+      ]
+    };
+    ngUtil.rewriteFile(socketConfig);
   }
-};
 
-Generator.prototype.createFiles = function createFiles() {
-  var dest = this.config.get('endpointDirectory') || 'server/api/' + this.name;
-  this.sourceRoot(path.join(__dirname, './templates'));
-  ngUtil.processDirectory(this, '.', dest);
+  if (this.filters.sequelize && this.config.get('insertModels')) {
+    var modelsFile = this.config.get('registerModelsFile');
+    var reqPath = this.relativeRequire(this.routeDest + '/' + this.basename +
+      '.model', modelsFile);
+    var modelConfig = {
+      file: modelsFile,
+      needle: this.config.get('modelsNeedle'),
+      splicable: [
+        "db." + this.classedName + " = db.sequelize.import(\'" + reqPath +"\');"
+      ]
+    };
+    ngUtil.rewriteFile(modelConfig);
+  }
 };
diff --git a/endpoint/templates/basename.controller.js b/endpoint/templates/basename.controller.js
new file mode 100644
index 000000000..cd151b13a
--- /dev/null
+++ b/endpoint/templates/basename.controller.js
@@ -0,0 +1,125 @@
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     <%= route %>              ->  index<% if (filters.models) { %>
+ * POST    <%= route %>              ->  create
+ * GET     <%= route %>/:id          ->  show
+ * PUT     <%= route %>/:id          ->  update
+ * DELETE  <%= route %>/:id          ->  destroy<% } %>
+ */
+
+'use strict';<% if (filters.models) { %>
+
+var _ = require('lodash');<% if (filters.mongooseModels) { %>
+var <%= classedName %> = require('./<%= basename %>.model');<% } if (filters.sequelizeModels) { %>
+var sqldb = require('<%= relativeRequire(config.get('registerModelsFile')) %>');
+var <%= classedName %> = sqldb.<%= classedName %>;<% } %>
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    <% if (filters.mongooseModels) { %>var updated = _.merge(entity, updates);
+    return updated.saveAsync()
+      .spread(function(updated) {<% }
+       if (filters.sequelizeModels) { %>return entity.updateAttributes(updates)
+      .then(function(updated) {<% } %>
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      <% if (filters.mongooseModels) { %>return entity.removeAsync()<% }
+         if (filters.sequelizeModels) { %>return entity.destroy()<% } %>
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}<% } %>
+
+// Gets a list of <%= classedName %>s
+exports.index = function(req, res) {<% if (!filters.models) { %>
+  res.json([]);<% } else { %>
+  <% if (filters.mongooseModels) { %><%= classedName %>.findAsync()<% }
+     if (filters.sequelizeModels) { %><%= classedName %>.findAll()<% } %>
+    .then(responseWithResult(res))
+    .catch(handleError(res));<% } %>
+};<% if (filters.models) { %>
+
+// Gets a single <%= classedName %> from the DB
+exports.show = function(req, res) {
+  <% if (filters.mongooseModels) { %><%= classedName %>.findByIdAsync(req.params.id)<% }
+     if (filters.sequelizeModels) { %><%= classedName %>.find({
+    where: {
+      _id: req.params.id
+    }
+  })<% } %>
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Creates a new <%= classedName %> in the DB
+exports.create = function(req, res) {
+  <% if (filters.mongooseModels) { %><%= classedName %>.createAsync(req.body)<% }
+     if (filters.sequelizeModels) { %><%= classedName %>.create(req.body)<% } %>
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+// Updates an existing <%= classedName %> in the DB
+exports.update = function(req, res) {
+  if (req.body._id) {
+    delete req.body._id;
+  }
+  <% if (filters.mongooseModels) { %><%= classedName %>.findByIdAsync(req.params.id)<% }
+     if (filters.sequelizeModels) { %><%= classedName %>.find({
+    where: {
+      _id: req.params.id
+    }
+  })<% } %>
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Deletes a <%= classedName %> from the DB
+exports.destroy = function(req, res) {
+  <% if (filters.mongooseModels) { %><%= classedName %>.findByIdAsync(req.params.id)<% }
+     if (filters.sequelizeModels) { %><%= classedName %>.find({
+    where: {
+      _id: req.params.id
+    }
+  })<% } %>
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};<% } %>
diff --git a/endpoint/templates/basename.events(models).js b/endpoint/templates/basename.events(models).js
new file mode 100644
index 000000000..f39b5b0be
--- /dev/null
+++ b/endpoint/templates/basename.events(models).js
@@ -0,0 +1,41 @@
+/**
+ * <%= classedName %> model events
+ */
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;<% if (filters.mongooseModels) { %>
+var <%= classedName %> = require('./<%= basename %>.model');<% } if (filters.sequelizeModels) { %>
+var <%= classedName %> = require('<%= relativeRequire(config.get('registerModelsFile')) %>').<%= classedName %>;<% } %>
+var <%= classedName %>Events = new EventEmitter();
+
+// Set max event listeners (0 == unlimited)
+<%= classedName %>Events.setMaxListeners(0);
+
+// Model events<% if (filters.mongooseModels) { %>
+var events = {
+  'save': 'save',
+  'remove': 'remove'
+};<% } if (filters.sequelizeModels) { %>
+var events = {
+  'afterCreate': 'save',
+  'afterUpdate': 'save',
+  'afterDestroy': 'remove'
+};<% } %>
+
+// Register the event emitter to the model events
+for (var e in events) {
+  var event = events[e];<% if (filters.mongooseModels) { %>
+  <%= classedName %>.schema.post(e, emitEvent(event));<% } if (filters.sequelizeModels) { %>
+  <%= classedName %>.hook(e, emitEvent(event));<% } %>
+}
+
+function emitEvent(event) {
+  return function(doc<% if (filters.sequelizeModels) { %>, options, done<% } %>) {
+    <%= classedName %>Events.emit(event + ':' + doc._id, doc);
+    <%= classedName %>Events.emit(event, doc);<% if (filters.sequelizeModels) { %>
+    done(null);<% } %>
+  }
+}
+
+module.exports = <%= classedName %>Events;
diff --git a/endpoint/templates/basename.integration.js b/endpoint/templates/basename.integration.js
new file mode 100644
index 000000000..067898d72
--- /dev/null
+++ b/endpoint/templates/basename.integration.js
@@ -0,0 +1,147 @@
+'use strict';
+
+var app = require('<%= relativeRequire('server') %>');
+var request = require('supertest');<% if(filters.models) { %>
+
+var new<%= classedName %>;<% } %>
+
+describe('<%= classedName %> API:', function() {
+
+  describe('GET <%= route %>', function() {
+    var <%= cameledName %>s;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('<%= route %>')
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          <%= cameledName %>s = res.body;
+          done();
+        });
+    });
+
+    it('should respond with JSON array', function() {
+      <%= cameledName %>s.should.be.instanceOf(Array);
+    });
+
+  });<% if(filters.models) { %>
+
+  describe('POST <%= route %>', function() {
+    beforeEach(function(done) {
+      request(app)
+        .post('<%= route %>')
+        .send({
+          name: 'New <%= classedName %>',
+          info: 'This is the brand new <%= cameledName %>!!!'
+        })
+        .expect(201)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          new<%= classedName %> = res.body;
+          done();
+        });
+    });
+
+    it('should respond with the newly created <%= cameledName %>', function() {
+      new<%= classedName %>.name.should.equal('New <%= classedName %>');
+      new<%= classedName %>.info.should.equal('This is the brand new <%= cameledName %>!!!');
+    });
+
+  });
+
+  describe('GET <%= route %>/:id', function() {
+    var <%= cameledName %>;
+
+    beforeEach(function(done) {
+      request(app)
+        .get('<%= route %>/' + new<%= classedName %>._id)
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          <%= cameledName %> = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      <%= cameledName %> = {};
+    });
+
+    it('should respond with the requested <%= cameledName %>', function() {
+      <%= cameledName %>.name.should.equal('New <%= classedName %>');
+      <%= cameledName %>.info.should.equal('This is the brand new <%= cameledName %>!!!');
+    });
+
+  });
+
+  describe('PUT <%= route %>/:id', function() {
+    var updated<%= classedName %>
+
+    beforeEach(function(done) {
+      request(app)
+        .put('<%= route %>/' + new<%= classedName %>._id)
+        .send({
+          name: 'Updated <%= classedName %>',
+          info: 'This is the updated <%= cameledName %>!!!'
+        })
+        .expect(200)
+        .expect('Content-Type', /json/)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          updated<%= classedName %> = res.body;
+          done();
+        });
+    });
+
+    afterEach(function() {
+      updated<%= classedName %> = {};
+    });
+
+    it('should respond with the updated <%= cameledName %>', function() {
+      updated<%= classedName %>.name.should.equal('Updated <%= classedName %>');
+      updated<%= classedName %>.info.should.equal('This is the updated <%= cameledName %>!!!');
+    });
+
+  });
+
+  describe('DELETE <%= route %>/:id', function() {
+
+    it('should respond with 204 on successful removal', function(done) {
+      request(app)
+        .delete('<%= route %>/' + new<%= classedName %>._id)
+        .expect(204)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+    it('should respond with 404 when <%= cameledName %> does not exist', function(done) {
+      request(app)
+        .delete('<%= route %>/' + new<%= classedName %>._id)
+        .expect(404)
+        .end(function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          done();
+        });
+    });
+
+  });<% } %>
+
+});
diff --git a/endpoint/templates/name.model(mongoose).js b/endpoint/templates/basename.model(mongooseModels).js
similarity index 60%
rename from endpoint/templates/name.model(mongoose).js
rename to endpoint/templates/basename.model(mongooseModels).js
index 89e0dfaa7..09787cdf3 100644
--- a/endpoint/templates/name.model(mongoose).js
+++ b/endpoint/templates/basename.model(mongooseModels).js
@@ -1,7 +1,7 @@
 'use strict';
 
-var mongoose = require('mongoose'),
-    Schema = mongoose.Schema;
+var mongoose = require('bluebird').promisifyAll(require('mongoose'));
+var Schema = mongoose.Schema;
 
 var <%= classedName %>Schema = new Schema({
   name: String,
@@ -9,4 +9,4 @@ var <%= classedName %>Schema = new Schema({
   active: Boolean
 });
 
-module.exports = mongoose.model('<%= classedName %>', <%= classedName %>Schema);
\ No newline at end of file
+module.exports = mongoose.model('<%= classedName %>', <%= classedName %>Schema);
diff --git a/endpoint/templates/basename.model(sequelizeModels).js b/endpoint/templates/basename.model(sequelizeModels).js
new file mode 100644
index 000000000..051c5daf2
--- /dev/null
+++ b/endpoint/templates/basename.model(sequelizeModels).js
@@ -0,0 +1,15 @@
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('<%= classedName %>', {
+    _id: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+      primaryKey: true,
+      autoIncrement: true
+    },
+    name: DataTypes.STRING,
+    info: DataTypes.STRING,
+    active: DataTypes.BOOLEAN
+  });
+};
diff --git a/endpoint/templates/basename.socket(socketio).js b/endpoint/templates/basename.socket(socketio).js
new file mode 100644
index 000000000..037f6113a
--- /dev/null
+++ b/endpoint/templates/basename.socket(socketio).js
@@ -0,0 +1,34 @@
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var <%= classedName %>Events = require('./<%= basename %>.events');
+
+// Model events to emit
+var events = ['save', 'remove'];
+
+exports.register = function(socket) {
+  // Bind model events to socket events
+  for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
+    var event = events[i];
+    var listener = createListener('<%= cameledName %>:' + event, socket);
+
+    <%= classedName %>Events.on(event, listener);
+    socket.on('disconnect', removeListener(event, listener));
+  }
+};
+
+
+function createListener(event, socket) {
+  return function(doc) {
+    socket.emit(event, doc);
+  };
+}
+
+function removeListener(event, listener) {
+  return function() {
+    <%= classedName %>Events.removeListener(event, listener);
+  };
+}
diff --git a/endpoint/templates/index.js b/endpoint/templates/index.js
index 03fdc9779..26dc430dd 100644
--- a/endpoint/templates/index.js
+++ b/endpoint/templates/index.js
@@ -1,15 +1,15 @@
 'use strict';
 
 var express = require('express');
-var controller = require('./<%= name %>.controller');
+var controller = require('./<%= basename %>.controller');
 
 var router = express.Router();
 
-router.get('/', controller.index);<% if(filters.mongoose) { %>
+router.get('/', controller.index);<% if (filters.models) { %>
 router.get('/:id', controller.show);
 router.post('/', controller.create);
 router.put('/:id', controller.update);
 router.patch('/:id', controller.update);
 router.delete('/:id', controller.destroy);<% } %>
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/endpoint/templates/index.spec.js b/endpoint/templates/index.spec.js
new file mode 100644
index 000000000..4bd178948
--- /dev/null
+++ b/endpoint/templates/index.spec.js
@@ -0,0 +1,97 @@
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var <%= cameledName %>CtrlStub = {
+  index: '<%= cameledName %>Ctrl.index'<% if(filters.models) { %>,
+  show: '<%= cameledName %>Ctrl.show',
+  create: '<%= cameledName %>Ctrl.create',
+  update: '<%= cameledName %>Ctrl.update',
+  destroy: '<%= cameledName %>Ctrl.destroy'<% } %>
+};
+
+var routerStub = {
+  get: sinon.spy()<% if(filters.models) { %>,
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()<% } %>
+};
+
+// require the index with our stubbed out modules
+var <%= cameledName %>Index = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './<%= basename %>.controller': <%= cameledName %>CtrlStub
+});
+
+describe('<%= classedName %> API Router:', function() {
+
+  it('should return an express router instance', function() {
+    <%= cameledName %>Index.should.equal(routerStub);
+  });
+
+  describe('GET <%= route %>', function() {
+
+    it('should route to <%= cameledName %>.controller.index', function() {
+      routerStub.get
+                .withArgs('/', '<%= cameledName %>Ctrl.index')
+                .should.have.been.calledOnce;
+    });
+
+  });<% if(filters.models) { %>
+
+  describe('GET <%= route %>/:id', function() {
+
+    it('should route to <%= cameledName %>.controller.show', function() {
+      routerStub.get
+                .withArgs('/:id', '<%= cameledName %>Ctrl.show')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST <%= route %>', function() {
+
+    it('should route to <%= cameledName %>.controller.create', function() {
+      routerStub.post
+                .withArgs('/', '<%= cameledName %>Ctrl.create')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT <%= route %>/:id', function() {
+
+    it('should route to <%= cameledName %>.controller.update', function() {
+      routerStub.put
+                .withArgs('/:id', '<%= cameledName %>Ctrl.update')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH <%= route %>/:id', function() {
+
+    it('should route to <%= cameledName %>.controller.update', function() {
+      routerStub.patch
+                .withArgs('/:id', '<%= cameledName %>Ctrl.update')
+                .should.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE <%= route %>/:id', function() {
+
+    it('should route to <%= cameledName %>.controller.destroy', function() {
+      routerStub.delete
+                .withArgs('/:id', '<%= cameledName %>Ctrl.destroy')
+                .should.have.been.calledOnce;
+    });
+
+  });<% } %>
+
+});
diff --git a/endpoint/templates/name.controller.js b/endpoint/templates/name.controller.js
deleted file mode 100644
index 3d46b2ad4..000000000
--- a/endpoint/templates/name.controller.js
+++ /dev/null
@@ -1,60 +0,0 @@
-'use strict';
-
-var _ = require('lodash');<% if (filters.mongoose) { %>
-var <%= classedName %> = require('./<%= name %>.model');<% } %>
-
-// Get list of <%= name %>s
-exports.index = function(req, res) {<% if (!filters.mongoose) { %>
-  res.json([]);<% } %><% if (filters.mongoose) { %>
-  <%= classedName %>.find(function (err, <%= name %>s) {
-    if(err) { return handleError(res, err); }
-    return res.status(200).json(<%= name %>s);
-  });<% } %>
-};<% if (filters.mongoose) { %>
-
-// Get a single <%= name %>
-exports.show = function(req, res) {
-  <%= classedName %>.findById(req.params.id, function (err, <%= name %>) {
-    if(err) { return handleError(res, err); }
-    if(!<%= name %>) { return res.status(404).send('Not Found'); }
-    return res.json(<%= name %>);
-  });
-};
-
-// Creates a new <%= name %> in the DB.
-exports.create = function(req, res) {
-  <%= classedName %>.create(req.body, function(err, <%= name %>) {
-    if(err) { return handleError(res, err); }
-    return res.status(201).json(<%= name %>);
-  });
-};
-
-// Updates an existing <%= name %> in the DB.
-exports.update = function(req, res) {
-  if(req.body._id) { delete req.body._id; }
-  <%= classedName %>.findById(req.params.id, function (err, <%= name %>) {
-    if (err) { return handleError(res, err); }
-    if(!<%= name %>) { return res.status(404).send('Not Found'); }
-    var updated = _.merge(<%= name %>, req.body);
-    updated.save(function (err) {
-      if (err) { return handleError(res, err); }
-      return res.status(200).json(<%= name %>);
-    });
-  });
-};
-
-// Deletes a <%= name %> from the DB.
-exports.destroy = function(req, res) {
-  <%= classedName %>.findById(req.params.id, function (err, <%= name %>) {
-    if(err) { return handleError(res, err); }
-    if(!<%= name %>) { return res.status(404).send('Not Found'); }
-    <%= name %>.remove(function(err) {
-      if(err) { return handleError(res, err); }
-      return res.status(204).send('No Content');
-    });
-  });
-};
-
-function handleError(res, err) {
-  return res.status(500).send(err);
-}<% } %>
\ No newline at end of file
diff --git a/endpoint/templates/name.socket(socketio).js b/endpoint/templates/name.socket(socketio).js
deleted file mode 100644
index 886f585ee..000000000
--- a/endpoint/templates/name.socket(socketio).js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Broadcast updates to client when the model changes
- */
-
-'use strict';
-
-var <%= classedName %> = require('./<%= name %>.model');
-
-exports.register = function(socket) {
-  <%= classedName %>.schema.post('save', function (doc) {
-    onSave(socket, doc);
-  });
-  <%= classedName %>.schema.post('remove', function (doc) {
-    onRemove(socket, doc);
-  });
-}
-
-function onSave(socket, doc, cb) {
-  socket.emit('<%= name %>:save', doc);
-}
-
-function onRemove(socket, doc, cb) {
-  socket.emit('<%= name %>:remove', doc);
-}
\ No newline at end of file
diff --git a/endpoint/templates/name.spec.js b/endpoint/templates/name.spec.js
deleted file mode 100644
index fcad73ebd..000000000
--- a/endpoint/templates/name.spec.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-var should = require('should');
-var app = require('../../app');
-var request = require('supertest');
-
-describe('GET <%= route %>', function() {
-
-  it('should respond with JSON array', function(done) {
-    request(app)
-      .get('<%= route %>')
-      .expect(200)
-      .expect('Content-Type', /json/)
-      .end(function(err, res) {
-        if (err) return done(err);
-        res.body.should.be.instanceof(Array);
-        done();
-      });
-  });
-});
\ No newline at end of file
diff --git a/factory/index.js b/factory/index.js
index 584079bad..c303eb9b8 100644
--- a/factory/index.js
+++ b/factory/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/filter/index.js b/filter/index.js
index 8aafad6f7..d1119b27d 100644
--- a/filter/index.js
+++ b/filter/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/generators/constant/index.js b/generators/constant/index.js
index fd7b5c574..4524a8eed 100644
--- a/generators/constant/index.js
+++ b/generators/constant/index.js
@@ -11,4 +11,4 @@ util.inherits(Generator, yeoman.generators.Base);
 
 Generator.prototype.deprecated = function deprecated() {
   this.log(chalk.yellow('This sub-generator is deprecated. \n'));
-};
\ No newline at end of file
+};
diff --git a/generators/deploy/index.js b/generators/deploy/index.js
index 6a3d5ec9c..7fb3452fa 100644
--- a/generators/deploy/index.js
+++ b/generators/deploy/index.js
@@ -12,4 +12,4 @@ util.inherits(Generator, yeoman.generators.NamedBase);
 Generator.prototype.deprecated = function deprecated() {
   this.log(chalk.yellow(chalk.bold('yo angular-fullstack:deploy') + ' is deprecated, instead use: \n') +
            chalk.green('yo angular-fullstack:heroku') + ' or ' + chalk.green('yo angular-fullstack:openshift'));
-};
\ No newline at end of file
+};
diff --git a/generators/readme.md b/generators/readme.md
index 670a62a57..d56c72138 100644
--- a/generators/readme.md
+++ b/generators/readme.md
@@ -1 +1 @@
-This folder is for deprecated generators only.
\ No newline at end of file
+This folder is for deprecated generators only.
diff --git a/generators/value/index.js b/generators/value/index.js
index fd7b5c574..4524a8eed 100644
--- a/generators/value/index.js
+++ b/generators/value/index.js
@@ -11,4 +11,4 @@ util.inherits(Generator, yeoman.generators.Base);
 
 Generator.prototype.deprecated = function deprecated() {
   this.log(chalk.yellow('This sub-generator is deprecated. \n'));
-};
\ No newline at end of file
+};
diff --git a/generators/view/index.js b/generators/view/index.js
index fd7b5c574..4524a8eed 100644
--- a/generators/view/index.js
+++ b/generators/view/index.js
@@ -11,4 +11,4 @@ util.inherits(Generator, yeoman.generators.Base);
 
 Generator.prototype.deprecated = function deprecated() {
   this.log(chalk.yellow('This sub-generator is deprecated. \n'));
-};
\ No newline at end of file
+};
diff --git a/openshift/USAGE b/openshift/USAGE
index b3dd18759..a57763b36 100644
--- a/openshift/USAGE
+++ b/openshift/USAGE
@@ -5,4 +5,4 @@ Example:
     yo angular-fullstack:openshift
 
     This will create:
-        a dist folder and initialize an openshift app
\ No newline at end of file
+        a dist folder and initialize an openshift app
diff --git a/openshift/index.js b/openshift/index.js
index 518806b08..7929c0e09 100644
--- a/openshift/index.js
+++ b/openshift/index.js
@@ -107,7 +107,7 @@ Generator.prototype.rhcAppShow = function rhcAppShow() {
       this.abort = true;
     }
     // No remote found
-    else if (stdout.search('not found.') < 0) {
+    else if (stdout.search('not found.') >= 0) {
       console.log('No existing app found.');
     }
     // Error
diff --git a/package.json b/package.json
index dbf3f3662..ccfcd3ff6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "generator-angular-fullstack",
-  "version": "2.1.1",
+  "version": "3.0.0-rc4",
   "description": "Yeoman generator for creating MEAN stack applications, using MongoDB, Express, AngularJS, and Node",
   "keywords": [
     "yeoman-generator",
@@ -26,39 +26,32 @@
     "test": "grunt test"
   },
   "dependencies": {
-    "yeoman-generator": "~0.17.0",
-    "chalk": "~0.4.0",
-    "wiredep": "~0.4.2",
-    "generator-ng-component": "~0.0.4"
-  },
-  "peerDependencies": {
-    "yo": ">=1.2.0"
+    "chalk": "^1.1.0",
+    "generator-ng-component": "~0.1.0",
+    "yeoman-generator": "~0.18.10"
   },
   "devDependencies": {
-    "chai": "^1.9.1",
-    "fs-extra": "^0.9.1",
+    "chai": "^3.2.0",
     "grunt": "~0.4.1",
-    "grunt-build-control": "DaftMonk/grunt-build-control",
+    "grunt-build-control": "^0.5.0",
     "grunt-contrib-clean": "^0.6.0",
-    "grunt-contrib-jshint": "^0.10.0",
+    "grunt-contrib-jshint": "^0.11.2",
     "grunt-conventional-changelog": "~1.0.0",
-    "grunt-mocha-test": "^0.11.0",
-    "grunt-release": "~0.6.0",
-    "load-grunt-tasks": "~0.2.0",
-    "marked": "~0.2.8",
-    "mocha": "~1.21.0",
+    "grunt-david": "~0.5.0",
+    "grunt-env": "^0.4.1",
+    "grunt-mocha-test": "^0.12.7",
+    "grunt-release": "^0.13.0",
+    "jit-grunt": "^0.9.1",
+    "mocha": "^2.2.5",
     "q": "^1.0.1",
-    "semver": "~2.2.1",
-    "shelljs": "^0.3.0",
-    "underscore.string": "^2.3.3"
+    "recursive-readdir": "^1.2.0",
+    "semver": "^5.0.1",
+    "shelljs": "^0.5.3",
+    "underscore.string": "^3.1.1"
   },
   "engines": {
-    "node": ">=0.10.0",
+    "node": ">=0.12.0",
     "npm": ">=1.2.10"
   },
-  "licenses": [
-    {
-      "type": "BSD"
-    }
-  ]
+  "license": "BSD-2-Clause"
 }
diff --git a/provider/index.js b/provider/index.js
index ed40ef29d..5e3ac882e 100644
--- a/provider/index.js
+++ b/provider/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/readme.md b/readme.md
index 21f73197a..f5f37e9e1 100644
--- a/readme.md
+++ b/readme.md
@@ -1,5 +1,5 @@
 # AngularJS Full-Stack generator
-[![Build Status](https://travis-ci.org/DaftMonk/generator-angular-fullstack.svg?branch=master)](http://travis-ci.org/DaftMonk/generator-angular-fullstack) [![npm version](https://badge.fury.io/js/generator-angular-fullstack.svg)](http://badge.fury.io/js/generator-angular-fullstack) ![](https://david-dm.org/daftmonk/generator-angular-fullstack.png) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/DaftMonk/generator-angular-fullstack)
+[![Build Status](https://travis-ci.org/DaftMonk/generator-angular-fullstack.svg?branch=master)](http://travis-ci.org/DaftMonk/generator-angular-fullstack) [![npm version](https://badge.fury.io/js/generator-angular-fullstack.svg)](http://badge.fury.io/js/generator-angular-fullstack) [![Dependency Status](https://david-dm.org/daftmonk/generator-angular-fullstack.png)](https://david-dm.org/daftmonk/generator-angular-fullstack) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/DaftMonk/generator-angular-fullstack)
 
 > Yeoman generator for creating MEAN stack applications, using MongoDB, Express, AngularJS, and Node - lets you quickly set up a project following best practices.
 
@@ -11,9 +11,9 @@ Source code: https://github.com/DaftMonk/fullstack-demo
 
 ## Usage
 
-Install `generator-angular-fullstack`:
+Install `yo`, `grunt-cli`, `bower`, and `generator-angular-fullstack`:
 ```
-npm install -g generator-angular-fullstack
+npm install -g yo grunt-cli bower generator-angular-fullstack
 ```
 
 Make a new directory, and `cd` into it:
@@ -263,7 +263,7 @@ To work with your new heroku app using the command line, you will need to run an
 
 If you're using mongoDB you will need to add a database to your app:
 
-    heroku addons:add mongolab
+    heroku addons:create mongolab
 
 Your app should now be live. To view it run `heroku open`.
 
@@ -302,7 +302,6 @@ The following packages are always installed by the [app](#app) generator:
 * angular-mocks
 * angular-resource
 * angular-sanitize
-* angular-scenario
 * es5-shim
 * font-awesome
 * json3
@@ -340,6 +339,19 @@ To setup protractor e2e tests, you must first run
 
 Use `grunt test:e2e` to have protractor go through tests located in the `e2e` folder.
 
+**Code Coverage**
+
+Use `grunt test:coverage` to run mocha-istanbul and generate code coverage reports.
+
+`coverage/server` will be populated with `e2e` and `unit` folders containing the `lcov` reports.
+
+The coverage taget has 3 available options:
+- `test:coverage:unit` generate server unit test coverage
+- `test:coverage:e2e` generate server e2e test coverage
+- `test:coverage:check` combine the coverage reports and check against predefined thresholds
+
+* *when no option is given `test:coverage` runs all options in the above order*
+
 ## Environment Variables
 
 Keeping your app secrets and other sensitive information in source control isn't a good idea. To have grunt launch your app with specific environment variables, add them to the git ignored environment config file: `server/config/local.env.js`.
diff --git a/route/index.js b/route/index.js
index 2a69476c5..cc8569854 100644
--- a/route/index.js
+++ b/route/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/script-base.js b/script-base.js
index 2f44ce09f..f24b61f85 100644
--- a/script-base.js
+++ b/script-base.js
@@ -15,11 +15,33 @@ var Generator = module.exports = function Generator() {
   this.appname = this._.slugify(this._.humanize(this.appname));
   this.scriptAppName = this._.camelize(this.appname) + angularUtils.appName(this);
 
-  this.cameledName = this._.camelize(this.name);
-  this.classedName = this._.classify(this.name);
+  var name = this.name.replace(/\//g, '-');
+
+  this.cameledName = this._.camelize(name);
+  this.classedName = this._.classify(name);
+
+  this.basename = path.basename(this.name);
+  this.dirname = (this.name.indexOf('/') >= 0) ? path.dirname(this.name) : this.name;
+
+  // dynamic assertion statement
+  this.does = this.is = function(foo) {
+    foo = this.engine(foo.replace(/\(;>%%<;\)/g, '<%')
+      .replace(/\(;>%<;\)/g, '%>'), this);
+    if (this.filters.should) {
+      return foo + '.should';
+    } else {
+      return 'expect(' + foo + ').to';
+    }
+  }.bind(this);
+
+  // dynamic relative require path
+  this.relativeRequire = function(to, fr) {
+    fr = fr || this.filePath;
+    return angularUtils.relativeRequire(this, to, fr);
+  }.bind(this);
 
   this.filters = this.config.get('filters');
   this.sourceRoot(path.join(__dirname, '/templates'));
 };
 
-util.inherits(Generator, yeoman.generators.NamedBase);
\ No newline at end of file
+util.inherits(Generator, yeoman.generators.NamedBase);
diff --git a/scripts/sauce_connect_setup.sh b/scripts/sauce_connect_setup.sh
new file mode 100755
index 000000000..4348e3662
--- /dev/null
+++ b/scripts/sauce_connect_setup.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+# Setup and start Sauce Connect for your TravisCI build
+# This script requires your .travis.yml to include the following two private env variables:
+# SAUCE_USERNAME
+# SAUCE_ACCESS_KEY
+# Follow the steps at https://saucelabs.com/opensource/travis to set that up.
+# https://gist.githubusercontent.com/santiycr/4683e262467c0a84d857/raw/1254ace59257e341ab200cbc8946e626756f079f/sauce-connect.sh
+
+if [ -z "${SAUCE_USERNAME}" ] || [ -z "${SAUCE_ACCESS_KEY}" ]; then
+    echo "This script can't run without your Sauce credentials"
+    echo "Please set SAUCE_USERNAME and SAUCE_ACCESS_KEY env variables"
+    echo "export SAUCE_USERNAME=ur-username"
+    echo "export SAUCE_ACCESS_KEY=ur-access-key"
+    exit 1
+fi
+ 
+SAUCE_TMP_DIR="$(mktemp -d -t sc.XXXX)"
+echo "Using temp dir $SAUCE_TMP_DIR"
+pushd $SAUCE_TMP_DIR
+ 
+SAUCE_CONNECT_PLATFORM=$(uname | sed -e 's/Darwin/osx/' -e 's/Linux/linux/')
+case "${SAUCE_CONNECT_PLATFORM}" in
+    linux)
+        SC_DISTRIBUTION_FMT=tar.gz;;
+    *)
+        SC_DISTRIBUTION_FMT=zip;;
+esac
+SC_DISTRIBUTION=sc-latest-${SAUCE_CONNECT_PLATFORM}.${SC_DISTRIBUTION_FMT}
+SC_READYFILE=sauce-connect-ready-$RANDOM
+SC_LOGFILE=$HOME/sauce-connect.log
+if [ ! -z "${TRAVIS_JOB_NUMBER}" ]; then
+  SC_TUNNEL_ID="-i ${TRAVIS_JOB_NUMBER}"
+fi
+echo "Downloading Sauce Connect"
+wget https://saucelabs.com/downloads/${SC_DISTRIBUTION}
+SC_DIR=$(tar -ztf ${SC_DISTRIBUTION} | head -n1)
+ 
+echo "Extracting Sauce Connect"
+case "${SC_DISTRIBUTION_FMT}" in
+    tar.gz)
+        tar zxf $SC_DISTRIBUTION;;
+    zip)
+        unzip $SC_DISTRIBUTION;;
+esac
+ 
+echo "Starting Sauce Connect"
+${SC_DIR}/bin/sc \
+  ${SC_TUNNEL_ID} \
+  -f ${SC_READYFILE} \
+  -l ${SC_LOGFILE} &
+ 
+echo "Waiting for Sauce Connect readyfile"
+while [ ! -f ${SC_READYFILE} ]; do
+  sleep .5
+done
+ 
+unset SAUCE_CONNECT_PLATFORM SAUCE_TMP_DIR SC_DIR SC_DISTRIBUTION SC_READYFILE SC_LOGFILE SC_TUNNEL_ID
+ 
+popd
diff --git a/service/index.js b/service/index.js
index d133abdbc..9aa5f48b0 100644
--- a/service/index.js
+++ b/service/index.js
@@ -7,4 +7,4 @@ var Generator = yeoman.generators.Base.extend({
   }
 });
 
-module.exports = Generator;
\ No newline at end of file
+module.exports = Generator;
diff --git a/test/fixtures/.yo-rc.json b/test/fixtures/.yo-rc.json
index b4b338ac4..01d568984 100644
--- a/test/fixtures/.yo-rc.json
+++ b/test/fixtures/.yo-rc.json
@@ -1,19 +1,36 @@
 {
   "generator-angular-fullstack": {
-    "insertRoutes": "true",
+    "endpointDirectory": "server/api/",
+    "insertRoutes": true,
     "registerRoutesFile": "server/routes.js",
     "routesNeedle": "// Insert routes below",
-    "insertSockets": "true",
+    "routesBase": "/api/",
+    "pluralizeRoutes": true,
+    "insertSockets": true,
     "registerSocketsFile": "server/config/socketio.js",
     "socketsNeedle": "// Insert sockets below",
+    "insertModels": true,
+    "registerModelsFile": "server/sqldb/index.js",
+    "modelsNeedle": "// Insert models below",
     "filters": {
       "coffee": true,
       "html": true,
       "less": true,
       "uirouter": true,
+      "bootstrap": false,
+      "uibootstrap": false,
       "socketio": true,
+      "auth": true,
+      "models": true,
+      "mongooseModels": true,
       "mongoose": true,
-      "auth": true
+      "oauth": true,
+      "googleAuth": true,
+      "grunt": true,
+      "mocha": true,
+      "jasmine": false,
+      "should": true,
+      "expect": false
     }
   }
-}
\ No newline at end of file
+}
diff --git a/test/fixtures/bower.json b/test/fixtures/bower.json
deleted file mode 100644
index 10dff6513..000000000
--- a/test/fixtures/bower.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "name": "tempApp",
-  "version": "0.0.0",
-  "dependencies": {
-    "angular": ">=1.2.*",
-    "json3": "~3.3.1",
-    "es5-shim": "~3.0.1",
-    "bootstrap-sass-official": "~3.1.1",
-    "bootstrap": "~3.1.1",
-    "angular-resource": ">=1.2.*",
-    "angular-cookies": ">=1.2.*",
-    "angular-sanitize": ">=1.2.*",
-    "angular-route": ">=1.2.*",
-    "angular-bootstrap": "~0.11.0",
-    "font-awesome": ">=4.1.0",
-    "lodash": "~2.4.1",
-    "angular-socket-io": "~0.6.0",
-    "angular-ui-router": "~0.2.10"
-  },
-  "devDependencies": {
-    "angular-mocks": ">=1.2.*",
-    "angular-scenario": ">=1.2.*"
-  }
-}
diff --git a/test/fixtures/package.json b/test/fixtures/package.json
deleted file mode 100644
index c110f7838..000000000
--- a/test/fixtures/package.json
+++ /dev/null
@@ -1,102 +0,0 @@
-{
-  "name": "tempApp",
-  "version": "0.0.0",
-  "main": "server/app.js",
-  "dependencies": {
-    "express": "~4.9.0",
-    "morgan": "~1.0.0",
-    "body-parser": "~1.5.0",
-    "method-override": "~1.0.0",
-    "serve-favicon": "~2.0.1",
-    "cookie-parser": "~1.0.1",
-    "express-session": "~1.0.2",
-    "errorhandler": "~1.0.0",
-    "compression": "~1.0.1",
-    "lodash": "~2.4.1",
-    "jade": "~1.2.0",
-    "ejs": "~0.8.4",
-    "mongoose": "~3.8.8",
-    "jsonwebtoken": "^0.3.0",
-    "express-jwt": "^0.1.3",
-    "passport": "~0.2.0",
-    "passport-local": "~0.1.6",
-    "passport-facebook": "latest",
-    "passport-twitter": "latest",
-    "passport-google-oauth": "latest",
-    "composable-middleware": "^0.3.0",
-    "connect-mongo": "^0.4.1",
-    "socket.io": "^1.0.6",
-    "socket.io-client": "^1.0.6",
-    "socketio-jwt": "^2.0.2"
-  },
-  "devDependencies": {
-    "grunt": "~0.4.4",
-    "grunt-autoprefixer": "~0.7.2",
-    "grunt-wiredep": "~1.8.0",
-    "grunt-concurrent": "~0.5.0",
-    "grunt-contrib-clean": "~0.5.0",
-    "grunt-contrib-concat": "~0.4.0",
-    "grunt-contrib-copy": "~0.5.0",
-    "grunt-contrib-cssmin": "~0.9.0",
-    "grunt-contrib-htmlmin": "~0.2.0",
-    "grunt-contrib-imagemin": "~0.7.1",
-    "grunt-contrib-jshint": "~0.10.0",
-    "grunt-contrib-uglify": "~0.4.0",
-    "grunt-contrib-watch": "~0.6.1",
-    "grunt-contrib-coffee": "^0.10.1",
-    "grunt-contrib-jade": "^0.11.0",
-    "grunt-contrib-less": "^0.11.0",
-    "karma-babel-preprocessor": "^5.2.1",
-    "grunt-babel": "~5.0.0",
-    "grunt-google-cdn": "~0.4.0",
-    "grunt-newer": "~0.7.0",
-    "grunt-ng-annotate": "^0.2.3",
-    "grunt-rev": "~0.1.0",
-    "grunt-svgmin": "~0.4.0",
-    "grunt-usemin": "~2.1.1",
-    "grunt-env": "~0.4.1",
-    "grunt-node-inspector": "~0.1.5",
-    "grunt-nodemon": "~0.2.0",
-    "grunt-angular-templates": "^0.5.4",
-    "grunt-dom-munger": "^3.4.0",
-    "grunt-protractor-runner": "^1.1.0",
-    "grunt-injector": "~0.5.4",
-    "grunt-karma": "~0.8.2",
-    "grunt-build-control": "DaftMonk/grunt-build-control",
-    "grunt-mocha-test": "~0.10.2",
-    "grunt-contrib-sass": "^0.7.3",
-    "grunt-contrib-stylus": "latest",
-    "jit-grunt": "^0.5.0",
-    "time-grunt": "~0.3.1",
-    "grunt-express-server": "~0.4.17",
-    "grunt-open": "~0.2.3",
-    "open": "~0.0.4",
-    "jshint-stylish": "~0.1.5",
-    "connect-livereload": "~0.4.0",
-    "karma-ng-scenario": "~0.1.0",
-    "karma-firefox-launcher": "~0.1.3",
-    "karma-script-launcher": "~0.1.0",
-    "karma-html2js-preprocessor": "~0.1.0",
-    "karma-ng-jade2js-preprocessor": "^0.1.2",
-    "karma-jasmine": "~0.1.5",
-    "karma-chrome-launcher": "~0.1.3",
-    "requirejs": "~2.1.11",
-    "karma-requirejs": "~0.2.1",
-    "karma-coffee-preprocessor": "~0.2.1",
-    "karma-jade-preprocessor": "0.0.11",
-    "karma-phantomjs-launcher": "~0.1.4",
-    "karma": "~0.12.9",
-    "karma-ng-html2js-preprocessor": "~0.1.0",
-    "supertest": "~0.11.0",
-    "should": "~3.3.1"
-  },
-  "engines": {
-    "node": ">=0.10.0"
-  },
-  "scripts": {
-    "start": "node server/app.js",
-    "test": "grunt test",
-    "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
-  },
-  "private": true
-}
diff --git a/test/readme.md b/test/readme.md
deleted file mode 100644
index 6c709f50b..000000000
--- a/test/readme.md
+++ /dev/null
@@ -1 +0,0 @@
-Run bower install and npm install in the fixtures folder before running tests
\ No newline at end of file
diff --git a/test/test-file-creation.js b/test/test-file-creation.js
index 594dab05a..7850fc9e4 100644
--- a/test/test-file-creation.js
+++ b/test/test-file-creation.js
@@ -1,43 +1,322 @@
 /*global describe, beforeEach, it */
 'use strict';
 var path = require('path');
+var fs = require('fs');
+var exec = require('child_process').exec;
 var helpers = require('yeoman-generator').test;
 var chai = require('chai');
 var expect = chai.expect;
-var fs = require('fs-extra');
-var exec = require('child_process').exec;
+var recursiveReadDir = require('recursive-readdir');
 
 describe('angular-fullstack generator', function () {
   var gen, defaultOptions = {
     script: 'js',
+    babel: true,
     markup: 'html',
     stylesheet: 'sass',
     router: 'uirouter',
+    testing: 'mocha',
+    chai: 'expect',
     bootstrap: true,
     uibootstrap: true,
-    mongoose: true,
+    odms: [ 'mongoose' ],
     auth: true,
     oauth: [],
     socketio: true
   }, dependenciesInstalled = false;
 
+  function copySync(s, d) { fs.writeFileSync(d, fs.readFileSync(s)); }
+
   function generatorTest(generatorType, name, mockPrompt, callback) {
-    gen.run({}, function () {
+    gen.run(function () {
       var afGenerator;
       var deps = [path.join('../..', generatorType)];
       afGenerator = helpers.createGenerator('angular-fullstack:' + generatorType, deps, [name]);
 
       helpers.mockPrompt(afGenerator, mockPrompt);
-      afGenerator.run([], function () {
+      afGenerator.run(function () {
         callback();
       });
     });
   }
 
+  /**
+   * Assert that only an array of files exist at a given path
+   *
+   * @param  {Array}    expectedFiles - array of files
+   * @param  {Function} done          - callback(error{Error})
+   * @param  {String}   topLevelPath  - top level path to assert files at (optional)
+   * @param  {Array}    skip          - array of paths to skip/ignore (optional)
+   *
+   */
+  function assertOnlyFiles(expectedFiles, done, topLevelPath, skip) {
+    topLevelPath = topLevelPath || './';
+    skip = skip || ['node_modules', 'client/bower_components'];
+
+    recursiveReadDir(topLevelPath, skip, function(err, actualFiles) {
+      if (err) { return done(err); }
+      var files = actualFiles.concat();
+
+      expectedFiles.forEach(function(file, i) {
+        var index = files.indexOf(path.normalize(file));
+        if (index >= 0) {
+          files.splice(index, 1);
+        }
+      });
+
+      if (files.length !== 0) {
+        err = new Error('unexpected files found');
+        err.expected = expectedFiles.join('\n');
+        err.actual = files.join('\n');
+        return done(err);
+      }
+
+      done();
+    });
+  }
+
+  /**
+   * Exec a command and run test assertion(s) based on command type
+   *
+   * @param  {String}   cmd      - the command to exec
+   * @param  {Object}   self     - context of the test
+   * @param  {Function} cb       - callback()
+   * @param  {String}   endpoint - endpoint to generate before exec (optional)
+   * @param  {Number}   timeout  - timeout for the exec and test (optional)
+   *
+   */
+  function runTest(cmd, self, cb) {
+    var args = Array.prototype.slice.call(arguments),
+        endpoint = (args[3] && typeof args[3] === 'string') ? args.splice(3, 1)[0] : null,
+        timeout = (args[3] && typeof args[3] === 'number') ? args.splice(3, 1)[0] : null;
+
+    self.timeout(timeout || 60000);
+
+    var execFn = function() {
+      var cmdCode;
+      var cp = exec(cmd, function(error, stdout, stderr) {
+        if(cmdCode !== 0) {
+          console.error(stdout);
+          throw new Error('Error running command: ' + cmd);
+        }
+        cb();
+      });
+      cp.on('exit', function (code) {
+        cmdCode = code;
+      });
+    };
+
+    if (endpoint) {
+      generatorTest('endpoint', endpoint, {}, execFn);
+    } else {
+      gen.run(execFn);
+    }
+  }
+
+  /**
+   * Generate an array of files to expect from a set of options
+   *
+   * @param  {Object} ops - generator options
+   * @return {Array}      - array of files
+   *
+   */
+  function genFiles(ops) {
+    var mapping = {
+      stylesheet: {
+        sass: 'scss',
+        stylus: 'styl',
+        less: 'less',
+        css: 'css'
+      },
+      markup: {
+        jade: 'jade',
+        html: 'html'
+      },
+      script: {
+        js: 'js',
+        coffee: 'coffee'
+      }
+    },
+    files = [];
+
+    /**
+     * Generate an array of OAuth files based on type
+     *
+     * @param  {String} type - type of oauth
+     * @return {Array}       - array of files
+     *
+     */
+    var oauthFiles = function(type) {
+      return [
+        'server/auth/' + type + '/index.js',
+        'server/auth/' + type + '/passport.js',
+      ];
+    };
+
+
+    var script = mapping.script[ops.script],
+        markup = mapping.markup[ops.markup],
+        stylesheet = mapping.stylesheet[ops.stylesheet],
+        models = ops.models ? ops.models : ops.odms[0];
+
+    /* Core Files */
+    files = files.concat([
+      'client/.htaccess',
+      'client/.jshintrc',
+      'client/favicon.ico',
+      'client/robots.txt',
+      'client/index.html',
+      'client/app/app.' + script,
+      'client/app/app.' + stylesheet,
+      'client/app/main/main.' + script,
+      'client/app/main/main.' + markup,
+      'client/app/main/main.' + stylesheet,
+      'client/app/main/main.controller.' + script,
+      'client/app/main/main.controller.spec.' + script,
+      'client/assets/images/yeoman.png',
+      'client/components/footer/footer.' + stylesheet,
+      'client/components/footer/footer.' + markup,
+      'client/components/footer/footer.directive.' + script,
+      'client/components/navbar/navbar.' + markup,
+      'client/components/navbar/navbar.controller.' + script,
+      'client/components/navbar/navbar.directive.' + script,
+      'server/.jshintrc',
+      'server/.jshintrc-spec',
+      'server/app.js',
+      'server/index.js',
+      'server/routes.js',
+      'server/api/thing/index.js',
+      'server/api/thing/index.spec.js',
+      'server/api/thing/thing.controller.js',
+      'server/api/thing/thing.integration.js',
+      'server/components/errors/index.js',
+      'server/config/local.env.js',
+      'server/config/local.env.sample.js',
+      'server/config/express.js',
+      'server/config/environment/index.js',
+      'server/config/environment/development.js',
+      'server/config/environment/production.js',
+      'server/config/environment/test.js',
+      'server/views/404.' + markup,
+      'e2e/main/main.po.js',
+      'e2e/main/main.spec.js',
+      'e2e/components/navbar/navbar.po.js',
+      '.bowerrc',
+      '.buildignore',
+      '.editorconfig',
+      '.gitattributes',
+      '.gitignore',
+      '.travis.yml',
+      '.jscs.json',
+      '.yo-rc.json',
+      'Gruntfile.js',
+      'package.json',
+      'bower.json',
+      'karma.conf.js',
+      'mocha.conf.js',
+      'protractor.conf.js',
+      'README.md'
+    ]);
+
+    /* Ui-Router */
+    if (ops.router === 'uirouter') {
+      files = files.concat([
+        'client/components/ui-router/ui-router.mock.' + script
+      ]);
+    }
+
+    /* Ui-Bootstrap */
+    if (ops.uibootstrap) {
+      files = files.concat([
+        'client/components/modal/modal.' + markup,
+        'client/components/modal/modal.' + stylesheet,
+        'client/components/modal/modal.service.' + script
+      ]);
+    }
+
+    /* Models - Mongoose or Sequelize */
+    if (models) {
+      files = files.concat([
+        'server/api/thing/thing.model.js',
+        'server/api/thing/thing.events.js',
+        'server/config/seed.js'
+      ]);
+    }
+
+    /* Sequelize */
+    if (ops.odms.indexOf('sequelize') !== -1) {
+      files = files.concat([
+        'server/sqldb/index.js'
+      ]);
+    }
+
+    /* Authentication */
+    if (ops.auth) {
+      files = files.concat([
+        'client/app/account/account.' + script,
+        'client/app/account/login/login.' + markup,
+        'client/app/account/login/login.' + stylesheet,
+        'client/app/account/login/login.controller.' + script,
+        'client/app/account/settings/settings.' + markup,
+        'client/app/account/settings/settings.controller.' + script,
+        'client/app/account/signup/signup.' + markup,
+        'client/app/account/signup/signup.controller.' + script,
+        'client/app/admin/admin.' + markup,
+        'client/app/admin/admin.' + stylesheet,
+        'client/app/admin/admin.' + script,
+        'client/app/admin/admin.controller.' + script,
+        'client/components/auth/auth.service.' + script,
+        'client/components/auth/user.service.' + script,
+        'client/components/mongoose-error/mongoose-error.directive.' + script,
+        'server/api/user/index.js',
+        'server/api/user/index.spec.js',
+        'server/api/user/user.controller.js',
+        'server/api/user/user.integration.js',
+        'server/api/user/user.model.js',
+        'server/api/user/user.model.spec.js',
+        'server/api/user/user.events.js',
+        'server/auth/index.js',
+        'server/auth/auth.service.js',
+        'server/auth/local/index.js',
+        'server/auth/local/passport.js',
+        'e2e/account/login/login.po.js',
+        'e2e/account/login/login.spec.js',
+        'e2e/account/logout/logout.spec.js',
+        'e2e/account/signup/signup.po.js',
+        'e2e/account/signup/signup.spec.js'
+      ]);
+    }
+
+    /* OAuth (see oauthFiles function above) */
+    if (ops.oauth) {
+      ops.oauth.forEach(function(type, i) {
+        files = files.concat(oauthFiles(type.replace('Auth', '')));
+      });
+    }
+
+    /* Socket.IO */
+    if (ops.socketio) {
+      files = files.concat([
+        'client/components/socket/socket.service.' + script,
+        'client/components/socket/socket.mock.' + script,
+        'server/api/thing/thing.socket.js',
+        'server/config/socketio.js'
+      ]);
+    }
+
+    return files;
+  }
+
+
+  /**
+   * Generator tests
+   */
+
   beforeEach(function (done) {
     this.timeout(10000);
     var deps = [
       '../../app',
+      '../../endpoint',
       [
         helpers.createDummyGenerator(),
         'ng-component:app'
@@ -55,6 +334,41 @@ describe('angular-fullstack generator', function () {
     }.bind(this));
   });
 
+  describe('making sure test fixtures are present', function() {
+
+    it('should have package.json in fixtures', function() {
+      helpers.assertFile([
+        path.join(__dirname, 'fixtures', 'package.json')
+      ]);
+    });
+
+    it('should have bower.json in fixtures', function() {
+      helpers.assertFile([
+        path.join(__dirname, 'fixtures', 'bower.json')
+      ]);
+    });
+
+    it('should have all npm packages in fixtures/node_modules', function() {
+      var packageJson = require('./fixtures/package.json');
+      var deps = Object.keys(packageJson.dependencies);
+      deps = deps.concat(Object.keys(packageJson.devDependencies));
+      deps = deps.map(function(dep) {
+        return path.join(__dirname, 'fixtures', 'node_modules', dep);
+      });
+      helpers.assertFile(deps);
+    });
+
+    it('should have all bower packages in fixtures/bower_components', function() {
+      var bowerJson = require('./fixtures/bower.json');
+      var deps = Object.keys(bowerJson.dependencies);
+      deps = deps.concat(Object.keys(bowerJson.devDependencies));
+      deps = deps.map(function(dep) {
+        return path.join(__dirname, 'fixtures', 'bower_components', dep);
+      });
+      helpers.assertFile(deps);
+    });
+  });
+
   describe('running app', function() {
 
     beforeEach(function() {
@@ -70,50 +384,70 @@ describe('angular-fullstack generator', function () {
       });
 
       it('should run client tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:client', function (error, stdout, stderr) {
-            expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1 SUCCESS');
-            done();
-          });
-        });
+        runTest('grunt test:client', this, done);
       });
 
-      it('should pass jshint', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt jshint', function (error, stdout, stderr) {
-            expect(stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+      it('should pass jscs', function(done) {
+        runTest('grunt jscs', this, done);
+      });
+
+      it('should pass lint', function(done) {
+        runTest('grunt jshint', this, done);
       });
 
       it('should run server tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:server', function (error, stdout, stderr) {
-            expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+        runTest('grunt test:server', this, done);
+      });
+
+      it('should pass jscs with generated endpoint', function(done) {
+        runTest('grunt jscs', this, done, 'foo');
+      });
+
+      it('should pass lint with generated endpoint', function(done) {
+        runTest('grunt jshint', this, done, 'foo');
       });
 
       it('should run server tests successfully with generated endpoint', function(done) {
-        this.timeout(60000);
-        generatorTest('endpoint', 'foo', {}, function() {
-          exec('grunt test:server', function (error, stdout, stderr) {
-            expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+        runTest('grunt test:server', this, done, 'foo');
+      });
+
+      it('should pass lint with generated capitalized endpoint', function(done) {
+        runTest('grunt jshint', this, done, 'Foo');
+      });
+
+      it('should run server tests successfully with generated capitalized endpoint', function(done) {
+        runTest('grunt test:server', this, done, 'Foo');
+      });
+
+      it('should pass lint with generated path name endpoint', function(done) {
+        runTest('grunt jshint', this, done, 'foo/bar');
+      });
+
+      it('should run server tests successfully with generated path name endpoint', function(done) {
+        runTest('grunt test:server', this, done, 'foo/bar');
+      });
+
+      it('should generate expected files with path name endpoint', function(done) {
+        runTest('(exit 0)', this, function() {
+          helpers.assertFile([
+            'server/api/foo/bar/index.js',
+            'server/api/foo/bar/index.spec.js',
+            'server/api/foo/bar/bar.controller.js',
+            'server/api/foo/bar/bar.events.js',
+            'server/api/foo/bar/bar.integration.js',
+            'server/api/foo/bar/bar.model.js',
+            'server/api/foo/bar/bar.socket.js'
+          ]);
+          done();
+        }, 'foo/bar');
       });
 
       it('should use existing config if available', function(done) {
         this.timeout(60000);
-        fs.copySync(__dirname + '/fixtures/.yo-rc.json', __dirname + '/temp/.yo-rc.json');
+        copySync(__dirname + '/fixtures/.yo-rc.json', __dirname + '/temp/.yo-rc.json');
         var gen = helpers.createGenerator('angular-fullstack:app', [
           '../../app',
+          '../../endpoint',
           [
             helpers.createDummyGenerator(),
             'ng-component:app'
@@ -123,232 +457,317 @@ describe('angular-fullstack generator', function () {
         helpers.mockPrompt(gen, {
           skipConfig: true
         });
-        gen.run({}, function () {
+        gen.run(function () {
           helpers.assertFile([
             'client/app/main/main.less',
-            'client/app/main/main.coffee'
+            'client/app/main/main.coffee',
+            'server/auth/google/passport.js'
           ]);
           done();
         });
       });
 
-//      it('should run e2e tests successfully', function(done) {
-//        this.timeout(80000);
-//        gen.run({}, function () {
-//          exec('npm run update-webdriver', function (error, stdout, stderr) {
-//            exec('grunt test:e2e', function (error, stdout, stderr) {
-//              expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Done, without errors.');
-//              done();
-//            });
-//          });
-//        })
-//      });
+      it('should generate expected files', function (done) {
+        gen.run(function () {
+          helpers.assertFile(genFiles(defaultOptions));
+          done();
+        });
+      });
+
+      it('should not generate unexpected files', function (done) {
+        gen.run(function () {
+          assertOnlyFiles(genFiles(defaultOptions), done);
+        });
+      });
+
+      if(!process.env.SKIP_E2E) {
+        it('should run e2e tests successfully', function(done) {
+          runTest('grunt test:e2e', this, done, 240000);
+        });
+
+        //it('should run e2e tests successfully for production app', function(done) {
+        //  runTest('grunt test:e2e:prod', this, done, 240000);
+        //});
+      }
     });
 
-    describe('with Babel ES6 preprocessor', function() {
+    describe('with other preprocessors and oauth', function() {
+      var testOptions = {
+        script: 'coffee',
+        markup: 'jade',
+        stylesheet: 'less',
+        router: 'uirouter',
+        testing: 'jasmine',
+        odms: [ 'mongoose' ],
+        auth: true,
+        oauth: ['twitterAuth', 'facebookAuth', 'googleAuth'],
+        socketio: true,
+        bootstrap: true,
+        uibootstrap: true
+      };
+
       beforeEach(function() {
-        helpers.mockPrompt(gen, {
-          script: 'js',
-          babel: true,
-          markup: 'jade',
-          stylesheet: 'less',
-          router: 'uirouter'
-        });
+        helpers.mockPrompt(gen, testOptions);
       });
 
       it('should run client tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:client', function (error, stdout, stderr) {
-            expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1 SUCCESS');
-            done();
-          });
-        });
+        runTest('grunt test:client', this, done);
       });
 
-      it('should pass jshint', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt jshint', function (error, stdout, stderr) {
-            expect(stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+      it('should pass jscs', function(done) {
+        runTest('grunt jscs', this, done);
+      });
+
+      it('should pass lint', function(done) {
+        runTest('grunt jshint', this, done);
       });
 
       it('should run server tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:server', function (error, stdout, stderr) {
-            expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
-            done();
-          });
+        runTest('grunt test:server', this, done);
+      });
+
+      it('should pass jscs with generated endpoint', function(done) {
+        runTest('grunt jscs', this, done, 'foo');
+      });
+
+      it('should pass lint with generated snake-case endpoint', function(done) {
+        runTest('grunt jshint', this, done, 'foo-bar');
+      });
+
+      it('should run server tests successfully with generated snake-case endpoint', function(done) {
+        runTest('grunt test:server', this, done, 'foo-bar');
+      });
+
+      it('should generate expected files', function (done) {
+        gen.run(function () {
+          helpers.assertFile(genFiles(testOptions));
+          done();
+        });
+      });
+
+      it('should not generate unexpected files', function (done) {
+        gen.run(function () {
+          assertOnlyFiles(genFiles(testOptions), done);
         });
       });
+
+      if(!process.env.SKIP_E2E) {
+        it('should run e2e tests successfully', function (done) {
+          runTest('grunt test:e2e', this, done, 240000);
+        });
+
+        //it('should run e2e tests successfully for production app', function (done) {
+        //  runTest('grunt test:e2e:prod', this, done, 240000);
+        //});
+      }
+
     });
 
+    describe('with sequelize models, auth', function() {
+      var testOptions = {
+        script: 'js',
+        markup: 'jade',
+        stylesheet: 'stylus',
+        router: 'uirouter',
+        testing: 'jasmine',
+        odms: [ 'sequelize' ],
+        auth: true,
+        oauth: ['twitterAuth', 'facebookAuth', 'googleAuth'],
+        socketio: true,
+        bootstrap: true,
+        uibootstrap: true
+      };
 
-    describe('with other preprocessors and oauth', function() {
       beforeEach(function() {
-        helpers.mockPrompt(gen, {
-          script: 'coffee',
-          markup: 'jade',
-          stylesheet: 'less',
-          router: 'uirouter',
-          mongoose: true,
-          auth: true,
-          oauth: ['twitterAuth', 'facebookAuth', 'googleAuth'],
-          socketio: true
-        });
+        helpers.mockPrompt(gen, testOptions);
       });
 
       it('should run client tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:client', function (error, stdout, stderr) {
-            expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1 SUCCESS');
-            done();
-          });
-        });
+        runTest('grunt test:client', this, done);
       });
 
-      it('should pass jshint', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt jshint', function (error, stdout, stderr) {
-            expect(stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+      it('should pass jscs', function(done) {
+        runTest('grunt jscs', this, done);
+      });
+
+      it('should pass lint', function(done) {
+        runTest('grunt jshint', this, done);
       });
 
       it('should run server tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:server', function (error, stdout, stderr) {
-            expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
-            done();
-          });
+        runTest('grunt test:server', this, done);
+      });
+
+      it('should pass jscs with generated endpoint', function(done) {
+        runTest('grunt jscs', this, done, 'foo');
+      });
+
+      it('should pass lint with generated snake-case endpoint', function(done) {
+        runTest('grunt jshint', this, done, 'foo-bar');
+      });
+
+      it('should run server tests successfully with generated snake-case endpoint', function(done) {
+        runTest('grunt test:server', this, done, 'foo-bar');
+      });
+
+      it('should generate expected files', function (done) {
+        gen.run(function () {
+          helpers.assertFile(genFiles(testOptions));
+          done();
+        });
+      });
+
+      it('should not generate unexpected files', function (done) {
+        gen.run(function () {
+          assertOnlyFiles(genFiles(testOptions), done);
         });
       });
+
+      if(!process.env.SKIP_E2E) {
+        it('should run e2e tests successfully', function (done) {
+          runTest('grunt test:e2e', this, done, 240000);
+        });
+
+        //it('should run e2e tests successfully for production app', function (done) {
+        //  runTest('grunt test:e2e:prod', this, done, 240000);
+        //});
+      }
+
     });
 
     describe('with other preprocessors and no server options', function() {
+      var testOptions = {
+        script: 'coffee',
+        markup: 'jade',
+        stylesheet: 'stylus',
+        router: 'ngroute',
+        testing: 'mocha',
+        chai: 'should',
+        odms: [],
+        auth: false,
+        oauth: [],
+        socketio: false,
+        bootstrap: false,
+        uibootstrap: false
+      };
+
       beforeEach(function(done) {
-        helpers.mockPrompt(gen, {
-          script: 'coffee',
-          markup: 'jade',
-          stylesheet: 'stylus',
-          router: 'ngroute',
-          mongoose: false,
-          auth: false,
-          oauth: [],
-          socketio: false
-        });
+        helpers.mockPrompt(gen, testOptions);
         done();
       });
 
       it('should run client tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:client', function (error, stdout, stderr) {
-            expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1 SUCCESS');
-            done();
-          });
-        });
+        runTest('grunt test:client', this, done);
       });
 
-      it('should pass jshint', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt jshint', function (error, stdout, stderr) {
-            expect(stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+      it('should pass jscs', function(done) {
+        runTest('grunt jscs', this, done);
+      });
+
+      it('should pass lint', function(done) {
+        runTest('grunt jshint', this, done);
       });
 
       it('should run server tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:server', function (error, stdout, stderr) {
-            expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
-            done();
-          });
+        runTest('grunt test:server', this, done);
+      });
+
+      it('should pass jscs with generated endpoint', function(done) {
+        runTest('grunt jscs', this, done, 'foo');
+      });
+
+      it('should pass lint with generated endpoint', function(done) {
+        runTest('grunt jshint', this, done, 'foo');
+      });
+
+      it('should run server tests successfully with generated endpoint', function(done) {
+        runTest('grunt test:server', this, done, 'foo');
+      });
+
+      it('should generate expected files', function (done) {
+        gen.run(function () {
+          helpers.assertFile(genFiles(testOptions));
+          done();
+        });
+      });
+
+      it('should not generate unexpected files', function (done) {
+        gen.run(function () {
+          assertOnlyFiles(genFiles(testOptions), done);
         });
       });
+
+      if(!process.env.SKIP_E2E) {
+        it('should run e2e tests successfully', function (done) {
+          runTest('grunt test:e2e', this, done, 240000);
+        });
+
+        //it('should run e2e tests successfully for production app', function (done) {
+        //  runTest('grunt test:e2e:prod', this, done, 240000);
+        //});
+      }
+
     });
 
     describe('with no preprocessors and no server options', function() {
+      var testOptions = {
+        script: 'js',
+        markup: 'html',
+        stylesheet: 'css',
+        router: 'ngroute',
+        testing: 'jasmine',
+        odms: [],
+        auth: false,
+        oauth: [],
+        socketio: false,
+        bootstrap: true,
+        uibootstrap: true
+      };
+
       beforeEach(function(done) {
-        helpers.mockPrompt(gen, {
-          script: 'js',
-          markup: 'html',
-          stylesheet: 'css',
-          router: 'ngroute',
-          mongoose: false,
-          auth: false,
-          oauth: [],
-          socketio: false
-        });
+        helpers.mockPrompt(gen, testOptions);
         done();
       });
 
       it('should run client tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:client', function (error, stdout, stderr) {
-            expect(stdout, 'Client tests failed \n' + stdout ).to.contain('Executed 1 of 1 SUCCESS');
-            done();
-          });
-        });
+        runTest('grunt test:client', this, done);
       });
 
-      it('should pass jshint', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt jshint', function (error, stdout, stderr) {
-            expect(stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+      it('should pass jscs', function(done) {
+        runTest('grunt jscs', this, done);
+      });
+
+      it('should pass lint', function(done) {
+        runTest('grunt jshint', this, done);
       });
 
       it('should run server tests successfully', function(done) {
-        this.timeout(60000);
-        gen.run({}, function () {
-          exec('grunt test:server', function (error, stdout, stderr) {
-            expect(stdout, 'Server tests failed (do you have mongoDB running?) \n' + stdout).to.contain('Done, without errors.');
-            done();
-          });
-        });
+        runTest('grunt test:server', this, done);
       });
 
       it('should generate expected files', function (done) {
-        helpers.mockPrompt(gen, defaultOptions);
-
-        gen.run({}, function () {
-          helpers.assertFile([
-            'client/.htaccess',
-            'client/favicon.ico',
-            'client/robots.txt',
-            'client/app/main/main.scss',
-            'client/app/main/main.html',
-            'client/index.html',
-            'client/.jshintrc',
-            'client/assets/images/yeoman.png',
-            '.bowerrc',
-            '.editorconfig',
-            '.gitignore',
-            'Gruntfile.js',
-            'package.json',
-            'bower.json',
-            'server/app.js',
-            'server/config/express.js',
-            'server/api/thing/index.js']);
+        gen.run(function () {
+          helpers.assertFile(genFiles(testOptions));
           done();
         });
       });
+
+      it('should not generate unexpected files', function (done) {
+        gen.run(function () {
+          assertOnlyFiles(genFiles(testOptions), done);
+        });
+      });
+
+      if(!process.env.SKIP_E2E) {
+        it('should run e2e tests successfully', function (done) {
+          runTest('grunt test:e2e', this, done, 240000);
+        });
+
+        //it('should run e2e tests successfully for production app', function (done) {
+        //  runTest('grunt test:e2e:prod', this, done, 240000);
+        //});
+      }
+
     });
   });
 });
diff --git a/util.js b/util.js
index 7544f8c8e..6657cfc9f 100644
--- a/util.js
+++ b/util.js
@@ -6,7 +6,8 @@ module.exports = {
   rewrite: rewrite,
   rewriteFile: rewriteFile,
   appName: appName,
-  processDirectory: processDirectory
+  processDirectory: processDirectory,
+  relativeRequire: relativeRequire
 };
 
 function rewriteFile (args) {
@@ -74,6 +75,23 @@ function appName (self) {
   return suffix ? self._.classify(suffix) : '';
 }
 
+function destinationPath (self, filepath) {
+  filepath = path.normalize(filepath);
+  if (!path.isAbsolute(filepath)) {
+    filepath = path.join(self.destinationRoot(), filepath);
+  }
+
+  return filepath;
+}
+
+function relativeRequire (self, to, fr) {
+  fr = destinationPath(self, fr);
+  to = destinationPath(self, to);
+  return path.relative(path.dirname(fr), to)
+    .replace(/^(?!\.\.)(.*)/, './$1')
+    .replace(/[\/\\]index\.js$/, '');
+}
+
 function filterFile (template) {
   // Find matches for parans
   var filterMatches = template.match(/\(([^)]+)\)/g);
@@ -109,6 +127,9 @@ function processDirectory (self, source, destination) {
 
   files.forEach(function(f) {
     var filteredFile = filterFile(f);
+    if(self.basename) {
+      filteredFile.name = filteredFile.name.replace('basename', self.basename);
+    }
     if(self.name) {
       filteredFile.name = filteredFile.name.replace('name', self.name);
     }
@@ -133,8 +154,10 @@ function processDirectory (self, source, destination) {
       if(copy) {
         self.copy(src, dest);
       } else {
+        self.filePath = dest;
         self.template(src, dest);
+        delete self.filePath;
       }
     }
   });
-}
\ No newline at end of file
+}