outlandish node app

0. Find us on Github

All of the code used and discussed in this tutorial can be found packaged up over at our Github account, ready-to-go. Download and unpack it so that you can inspect the code in more detail as you follow along, or just have a look around yourself and start tinkering! If you’re interested in getting the application running straight away, follow the instructions at the end of this post.


1. Introduction

In this tutorial we will help you start fresh with MEAN by showing you how to build a simple AJAX user authentication system. Not only will we cover the four obvious components of MEAN, but also some other popular libraries, such as these:

  • Brunch: a Node build system which avoids the pain of writing manual tasks such as those in Grunt or Gulp. Brunch can package, minify, and watch all of your JavaScript and CSS files out-of-the-box.
  • Sass: a popular CSS preprocessor which provides syntactic sugar, mixins, varibles, and more, to your stylesheets. All the stuff that makes writing CSS a breeze.
  • Passport: authentication middleware for Node which takes the difficulties out of OAuth authentication with social network sign ins (something we will not cover) and local sign ins (something we will cover) through providing a simple API with which developers can create plug-in third-party libraries with; for example, passport-facebook or passport-local.

Sounds like a lot, right? Well, it is, but don’t let that discourage you. The advantages of using these tools are many, which is why we want to share our experience of using them with you. At Outlandish we always jump at the chance to use new and exciting technologies. So when we were asked by the BBC’s R&D department to do just this, we went the full monty (but kept all our clothes on) and switched out our traditional LAMP stack for the new-kid-on-the-block, MEAN. To those of you folks who have yet to catch the MEAN bug, let me fill you in. MEAN is a full JavaScript development stack that consists of:

  • MongoDB: a NoSQL database, interfaced using the Mongoose Node.js library.
  • Express: a Node.js library that takes all the pain out of handling HTTP requests/responses.
  • AngularJS: a front-end JavaScript framework by Google that introduces logic and data-binding into your mark-up.
  • Node.JS: asynchronous, server-side JavaScript

Now put it all together! Yup – you just got MEANer! Before we get started though, here are a few KIND words of advice, curated from our own experiences of using these new technologies for the first time:

  • Kallbacks: Programming in Node’s asynchronous environment can be confusing. For example, sometimes you might find an assignment produce unexpected results, such as when a function is executed asynchronously and the value of your variable isn’t initialised on-the-spot. Remember to use those pesky callbacks! If all the indentation gives you a headache, it’s worth noting that there are libraries which return synchronous functionality to Node on a case-basis, such as with exec-sync, or as wrapper utilities, like async.
  • Inside-out: It’s easy to forget or overlook the functionality that Angular provides in its native directives, like the handy ng-repeat. So when you’re building your application, try to think ‘inside-out’: lots of logic that would otherwise sit in external scripts can be moved into your mark-up.
  • Nodemon: When you first start developing using Node, you might notice that you are constantly running node <filename>.js in order to boot the server with any fresh changes. Install nodemon using npm install -g nodemon and run nodemon <filename.js> instead – the server will reboot automatically when changes are detected. Alternatively, if you continue reading, find out how to use Brunch to watch your files and refresh your webpage automatically with all your new changes (it’s magic!).
  • Debug: You’ll need to debug your Node app now and again… but how? Using node-inspector you’ve got access to a fully-featured debugging interface which includes breakpoints, stepping, scope inspection, and much more. If you want to use it alongside nodemon then you’ll need to first run it as a server (just run nodemon) and invoke debugging on an instance of your app by using the –debug flag. More information on that here. Or, if like us you use PHPStorm, you’ve got debugging right there in your IDE. Wonderful.

It’s worth mentioning before we continue that of course you will need Node, MongoDB, and Sass (with Ruby!) installed/running on your local machine to get the most out of this tutorial and to see the finished product running in your browser! Now let’s move on to the first part of this tutorial by defining the structure of our web app…


2. Application File Structure

/anOutlandishWebApp
|  config.coffee
|  bower.json
|  package.json
|  README.md
|  server.js
|
└─ /angular
|    |  app.js
|    |  app.sass
|    |
|    └─ /routes 
|    |    └─ /login
|    |    |    login.html
|    |    |    login.sass
|    |    |    LoginController.js
|    |    └─ /welcome
|    |         welcome.html
|    |         welcome.sass
|    |         WelcomeController.js
|    |
|    └─ /services
|         User.js
|
└─ /app
|    |  config.js
|    |
|    └─ /models
|    |    User.js
|    └─ /routes
|    |    api.js
|    |    passport.js
|    └─ /views
|         error.jade
|         index.jade
|
└─ /public*
└─ /bower_components*
└─ /node_modules*
 

*Note: The contents of the bower_component, node_modules, and public directories are maintained by Bower, npm, and Brunch, respectively, but shown here for completeness.

Most of the files and folders within the application are self-explanatory, such as the angular and public folders. Just in case: angular is where anything concerning AngularJS will go, public will contain all compiled JavaScript and CSS files and will act as our static file path in Express (a request by the client for /js/main.js will resolve to /public/js/main.js on the server, for example), and finally app will contain all server-side NodeJS stuff.

  • 2.1. Package Managers: In the root of our file structure we have two files package.json and bower.json. These tell npm and Bower what dependencies our application has, what versions of these dependencies our application desires, and other metadata such as a title and description of the product. There is also a private property, which we set to true as we don’t want to publish this application to either npm or Bower’s public repository. Check out the git repository to see the full contents of these files – and if you do, you may notice that our dependencies’ version numbers have a caret (^) prepended to them. This modifier tells npm/Bower to ‘get the version whose release number is closest to and compatible with x.x.x’.

3. Configuration

Any Node application will require some kind of configuration. In this case the configuration file (app/config.js) will simply contain database credentials and session secrets for each our development and production environments (see 4.1 for more details on execution environments). This is what it looks like:

module.exports = {
    dev: {
        sessionSecret: 'developmentSessionSecret',
        db: {
            name: 'anOutlandishDatabase',
            host: 'localhost',
            username: '',
            password: ''
        }
    },
    prod: {
        sessionSecret: 'productionSessionSecret',
        db: {
            name: 'anOutlandishDatabase',
            host: 'productionHost',
            username: 'productionDatabaseUsername',
            password: 'productionDatabasePassword'
        }
    }
};

4. Creating the Server

So now we can start getting our hands dirty laying the foundations of our web app – by loading in our models, defining our routes, and tying them all together with an Express server. Let’s take a look at the most important parts of server.js, where all this happens.

  • 4.1. Execution Environment: When running a Node application on your local machine (during development or testing) there will usually exist some requirement for disclosure of errors to the client (you or testers). However, you also don’t want to expose errors to the client when the application is running in a production environment. So, on each our development and production machines we set an environment variable which the application can listen in on. Let’s call it NODE_ENV. In server.js we can access this variable like so:
    var ENV = process.env.NODE_ENV || 'prod'; // Assume production env. for safety
    var config = require('./app/config.js')[ENV]; // Get the configuration object for this execution environment
    
  • 4.2. Mongoose Configuration: Until now we haven’t mentioned mongoose. Mongoose is a Node library for interfacing with a MongoDB database. It requires some configuration such as defining a host and providing credentials for accessing a database. Let’s take a look:
    var connect = function() {
        var db = config.db;
        var conn = 'mongodb://' + db.username + ':' + db.password + '@' + db.host;
        (ENV === 'dev') ? mongoose.connect(db.host, db.name) : mongoose.connect(conn);
    };
    connect();
    
    mongoose.connection.on('error', function(err) {
        console.log(err);
    });
    
    if(ENV === 'prod') {
        mongoose.connection.on('disconnected', function() {
            connect();
        });
    };
    
    // 'require' all of our Mongo models:
    var models = __dirname + 'app/models';
    fs.readdirSync(models).forEach(function(model) {
        if(model.indexOf('.js') > -1) {
            require(models + '/' + model);
        }
    });
    

    So already we can see that depending on our execution environment we perform different actions when 1) connecting to our database and 2) re-connecting to the database should the connection be dropped (production only).

  • 4.3. Express Configuration: Next up for configuration is Express. Express utilises middleware which hook into some part of its control flow and modify requests with anything from appended convenience methods, such as isAuthenticated() as seen when using Passport, to full-blown parsing of a request’s body so that we can access form data, etc., as seen by bodyParser, an Express library. And here is how we do it:
    var sessionOpts = {
        secret: config.sessionSecret,
        resave: true,
        saveUninitialized: true,
        store: sessionStore
    };
    
    // Define where our static files will be fetched from:
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(cookieParser);
    app.use(session(sessionOpts));
    
    // Push Passport middleware into flow:
    app.use(passport.initialize());
    app.use(passport.session());
    
    // Tell Express where our server views (Jade files) are kept.
    // Then we can do render('NAME_OF_VIEW') inside an Express route request. e.g. render('index')
    app.set('views', path.join(__dirname, 'app/views'));
    app.set('view engine', 'jade');
    
  • 4.4. Route Configuration: We need to tell our Express application what to do when a user makes a request to specific routes. We do this using Express’ verb methods. The verb methods are in keeping with all the possible types of HTTP requests, such as GETPOSTDELETE, etc. As you can see from the file structure of our application we can pass our Express application instance as a parameter to different files within the app which will then define routes for various components of the program, such as the API and Passport authentication. But before we take a look at those routes specifically, let’s pull in the files where they are declared and define the main route [GET /*] which will delegate most GET requests that aren’t intercepted by our API (more on that later) to Angular:
    require('./app/routes/passport')(app, config);
    require('./app/routes/api')(app);
    
    app.get('/log-out', function(req, res) {
        req.logout(); // Log the user out
        res.redirect('/login'); // Send them back to the login page
    });
    
    app.get('/*', function (req, res) {
        // Render index and pass route handling to Angular
        res.render('index');
    });
    
    app.all('/*', function(req, res) {
        // Client is lost... render static error page!
        res.render('error');
    });
    
    console.log('Application running in ' + ENV + ' environment...');
    

5. Define Mongoose Models

Now that we have an Express server configured and ready to run we need to define a Mongoose user model so that we can create and maintain a list of all registered users. This is really simple and is accomplished through instantiating the mongoose.Schema class with all of the properties of the user that we want. This is all done inside app/models/User.js and looks like this:

var bcrypt = require('bcrypt-nodejs'),
    mongoose = require('mongoose');

var Schema = mongoose.Schema;

var UserSchema = new Schema({
    email: { type: String, index: true, required: true },
    password: { type: String, required: true }
});

UserSchema.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

UserSchema.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.password);
};

var User = mongoose.model('User', UserSchema);

module.exports = User;

As you can see we use a library, bcrypt-nodejs, to hash our users’ passwords so that we are never storing them in plaintext. All that we want to store in this case is an email and password so that a user can log in to the application, the functionality of which is next up on our list. But before that let’s discuss what’s going on when we declare methods on the UserSchema.methods property. Any instance of mongoose.Schema has a methods property which we can use to append functions that we can execute on an instance of that schema. For example, each User instance in our application will have it’s own validPassword method where we can check a string against this.password, which refers to that particular user’s hashed password, therefore performing an authentication check. This will be used during the login process…


6. Configuring Passport

So now we have defined our User model in Mongoose we can use it to perform authentication and creation of new users during the sign up process. Passport hooks into Express and does all the heavy-lifting for us, so that all we need to do is 1) define what routes perform authentication calls, and 2) where to find/create a user where necessary. This happens in app/routes/passport.js, the contents of which are fairly long so here we will cover the important parts:

  • 6.1. Serialisation of an Instance of User: All we are doing here is instructing Passport what to do when it receives a User instance and when it places an instance onto our Express req object as req.user. We don’t need to do anything except pass the instance (user) back to the callback untouched.
    passport.serializeUser(function(user, done) {
        done(null, user);
    });
    
    passport.deserializeUser(function(obj, done) {
        done(null, obj);
    });
    
  • 6.2. Declare Passport Authentication Strategies: Passport needs to know what strategies we want to use to authenticate. We can tell it by calling passport.use() with two parameters: a name for the authentication strategy, and an instance of that strategy (in our case LocalStrategy). We will take a look at the ‘local_signup’ strategy below, but you can find the login strategy over in the Github repository.
    passport.use('local_signup', new LocalStrategy({
        // Define what form 'name' fields to use as username and password equivalents:
        usernameField: 'email',
        passwordField: 'password'
    },
    function(email, password, done) {
        process.nextTick(function() {
            // Attempt to find a user with this email:
            User.findOne({
                email: email
            }, function(err, user) {
                if(err) {
                    done(err);
                    return;
                }
    
                if(user) {
                    done(null, false, { message: 'Email already in use.' });
                    return;
                }
    
                // User does not exist with this email, so create it:
                var newUser = new User();
    
                newUser.email = email;
                newUser.password = newUser.generateHash(password); // Call instance method 'generateHash' to produce password hash
    
                // Save this user to our database and return it to Passport by passing it as the second
                // parameter of the 'done' callback:
                newUser.save(function(err) {
                    if(err) {
                        done(err);
                        return;
                    }
    
                    done(null, newUser);
                });
            })
        });
    }));
    
  • 6.3. Declare Passport Authentication Routes: Now that Passport knows about our local authentication strategy we can hook it up to a route, so that the client can make a request to sign up. Again, we’ll only take a look at the route for creating a user, but you can find the route for logging in a user in the Github repository. Note that we are simply declaring a usual Express route with the verb method app.post which will accept POST requests at /auth/local/signup and forward them on to passport.authenticate:
    app.post('/auth/local/signup', function(req, res) {
        function handleError(status) {
            res.status(status).send();
        }
    
        // Call on Passport to authenticate using our 'local_signup' strategy. The second parameter will
        // be called on completion of the user creation, etc.
        passport.authenticate('local_signup', function(err, user) {
            if(err) {
                handleError(500);
                return;
            }
    
            if(!user) {
                handleError(401);
                return;
            }
    
            // Authentication was successful, 'login' this new user by creating a session with `req.login`:
            req.login(user, function(err) {
                if(err) {
                    handleError(500);
                    return;
                }
    
                res.send({ success: true });
            });
        })(req, res);
    });
    

7. Create Static Views

Although we are using Angular, which will deal with most of our view logic, we still need to respond to an initial request from the client with an index page which will in turn load Angular as it is after all a client-side framework. Doing this is really easy and starts with creating two views, one for a successful request (a request to a route we recognise) and an unsuccessful request (a 404, for routes we do not recognise). Let’s call them index.jade and error.jade, respectively. If you don’t know why the files are suffixed .jade, or you just aren’t familiar with the Jade templating engine, take a look at it’s documentation over at jade-lang.com.

  • 7.1. Index View: A really simple index page that defines a single container element which we will use as the view container for our app, by giving it the ng-view attribute. This element also has a class wrapper so we can reference it in our stylesheets. The index file will pull in our compiled stylesheet and JavaScript files too.
    doctype html
    html(lang="en", ng-app="anOutlandishWebApp")
        head
            title {{ title }}
            base(href="/")
            link(rel="stylesheet", href="/css/app.css")
        body
            div(ng-view, class="wrapper {{ className }}")
            script(src="/js/vendor.js", type="text/javascript")
            script(src="/js/templates.js", type="text/javascript")
            script(src="/js/app.js", type="text/javascript")
    

    Also notice the {{ title }} binding declaration. This gives Angular control over the title element in the page so that we can change it client-side when a view changes.

  • 7.2. Error View: An even simpler error page that tells the user they have stumbled into the deep, dark unknown:
    doctype html
    html(lang="en", ng-app="anOutlandishWebApp")
        head
            title Page Not Found
        body
            p 404 - Page Not Found
            p Uh oh! Looks like you found a page that doesn't exist?
    

8. API Routes

Now that we have in place all back-end components that deal with authentication, serving static views, persisting model instances to a database, and more, we can complete it at last by defining an API route for fetching a user’s details from the server. This happens in app/routes/api.js, which is passed our Express app instance, to which we will add a single route for doing exactly this:

module.exports = function(app) {
    var mongoose = require('mongoose'),
        User = mongoose.model('User');

    app.get('/api/user', function(req, res) {
        // Check if user is authenticated. If not, disallow access to the API:
        if(!req.isAuthenticated()) {
            res.status(401).send();
            return;
        }

        // User is authenticated so send them their `req.user` session object:
        res.send(req.user);
    });
};

9. Building an Angular Module, its Views, Controllers, and Services

Finally, with the back-end completed we can move on to the front-end! AngularJS is a lot to learn and trial and error are your best friend, but we can give you a helping hand by showing you the basics of services, controllers, and use of the $scope object. $scope will be something you’ll become very familiar with while using Angular. It is your interface between a controller (or directive, but don’t worry about those just yet) and a view. So let’s begin…

  • 9.1. Main Angular Application File – Routing and Configuration: Each Angular application has a file that declares a module for itself. All this does is tell the global angular runtime that our application exists so that we can access it across any number of files and always get a reference to the same application. After notifying Angular of our application we can then configure it, which means declare routes, etc. Here’s file in question, app.js (it’s long!):
    angular
        .module('anOutlandishWebApp', ['ngRoute', 'templates'])
        .config(function($routeProvider, $locationProvider) {
            var baseUrl = 'angular/routes';
    
            var resolvers = {
                fetchUser: function($q, $location, User) {
                    var defer = $q.defer();
    
                    User.load(function(err) {
                        if(err) {
                            $location.path('/login');
                            return;
                        }
    
                        defer.resolve();
                    });
    
                    return defer.promise;
                },
                redirectWithUser: function($q, $location, User) {
                    var defer = $q.defer();
    
                    User.load(function(err, user) {
                        if(err || !user) {
                            defer.resolve();
                            return;
                        }
    
                        $location.path('/welcome');
                    });
    
                    return defer.promise;
                }
            };
    
            $routeProvider
                .when('/login', {
                    templateUrl: baseUrl + '/login/login.html',
                    controller: 'LoginController',
                    resolve: { redirectWithUser: resolvers.redirectWithUser }
                })
                .when('/welcome', {
                    templateUrl: baseUrl + '/welcome/welcome.html',
                    controller: 'WelcomeController',
                    authenticate: true,
                    resolve: { fetchUser: resolvers.fetchUser }
                })
                .otherwise({
                    redirectTo: '/login'
                });
    
            $locationProvider.html5Mode(true);
        });
    

    As you can see, using $routeProvider we declare the different routes available in the application. For us, these are /login and /welcome. For each route we define the name of the controller that will be used when it is active, a URL to the template of the view, and a resolver. What is a resolver? Well, the resolve property allows us to tell Angular not to initialise the controller or view associated with a route until the functions within the object passed to resolve are resolved or fulfilled. This is accomplished by using JavaScript promises, given to us in this instance by the $q Angular dependency. Overall, this means we can restrict access to our /welcome route to those users who are authenticated, and redirect from /login when the client already has an active session.

    Before we move on, notice that in defining an Angular module you can pass an array of other modules, which are dependencies. Here we use ngRoute, Angular’s routing module, and templates which is a module of cached template files, kindly generated by Brunch.

  • 9.2. User Factory: An Angular factory allows us to create a dependency which persists data throughout the life of a session, which can then be accessed in controllers, etc. You could also use a service or provider, but we won’t get in to the differences here (feeling masochistic?). Ours looks like this:
    angular
        .module('anOutlandishWebApp')
        .factory('User', function($rootScope, $http) {
            "use strict";
    
            var methods = {},
                _user = null;
    
            methods.authenticate = function(email, password, done) {
                $http.post('/auth/local/signin', {
                    email: email,
                    password: password
                }).then(function(res) {
                    methods.load(done);
                }, function(err) {
                    done(err);
                })
            };
    
            methods.create = function(email, password, done) {
                $http.post('/auth/local/signup', {
                    email: email,
                    password: password
                }).then(function(res) {
                    done();
                }, function(err) {
                    done(err);
                });
            };
    
            methods.load = function(done) {
                $http.get('/api/user').then(function(res) {
                    _user = res.data;
                    $rootScope.user = res.data;
                    done(null, _user);
                }, function(err) {
                    done(err);
                });
            };
    
            methods.get = function() {
                return _user;
            };
    
            return methods;
        });
    

    An Angular factory, when injected as a dependency elsewhere in the application, makes whatever is returned from its point of declaration publicly available. So, in this case, all properties of the methods object are publicly accessible through User.methodName(). You will notice all methods accept at least one parameter of the same name, done – this is a callback and will be invoked on completion of the methods body. For example, if you call User.load() the callback given to it will be invoked once the server has returned with data of the user. If the method fails, the callback will be passed the error as in done(err).

    All AJAX calls performed within our User factory are done using the $http service, which provides really easy-to-use HTTP verb methods, similar to that which we saw in Express’ routing library. For each call we want to make, we just choose the appropriate verb and give it the endpoint we want to call. For example, to load user data we make a GET call to our /api/user endpoint which we defined earlier on the back-end, and which returns the user object for the active session.

  • 9.3. Creating our Views and their Controllers: For each route we want to create a folder which contains a corresponding template, stylesheet, and controller. For this bit let’s just take a look at these components for the /login route, as it’s more interesting. It will contain buttons for both signing up and logging in. First up, the controller:
    angular
        .module('anOutlandishWebApp')
        .controller('LoginController', function($scope, $rootScope, $location, User) {
            "use strict";
    
            $rootScope.className = 'login';
    
            $scope.signUp = function($event) {
                $event.preventDefault();             
    
                User.create($scope.email, $scope.password, function(err) {
                    if(err) {
                        alert("Could not sign up. Please try again later.");
                        return;
                    }
    
                    // User created successfully. Now sign them in automatically:
                    $scope.logIn();
                });
            };
    
            $scope.logIn = function($event) {
                $event.preventDefault();             
    
                User.authenticate($scope.email, $scope.password, function(err) {
                    if(err) {
                        alert("Could not log you in. Did you enter the correct credentials?");
                        return;
                    }
    
                    // Redirect the user on successful authentication:
                    $location.path('/welcome');
                });
            };
        });
    

    As you can see, we have created two methods on $scope, signUp and logIn. These will be available in our template, where we will use the ng-click directive on our form’s submit input to invoke these methods instead of allowing the conventional browser form submission. Both accept a parameter $event which we can use to do this, by calling preventDefault.

    Next, our login view:

    <form>
        <h1>Hey there!</h1>
        <p>Please login or sign up:</p>
        <hr />
        <input type="text" placeholder="Email address" ng-model="email" />
        <input type="password" placeholder="Password" ng-model="password" />
        <input type="submit" ng-click="logIn($event)" value="Log In" />
        <input type="submit" ng-click="signUp($event)" value="Sign Up" />
    </form>
    

    Pretty simple stuff. The ng-model directives on our text inputs create properties on the scope with the name given within the string literal, for example our $scope will have a property $scope.email that updates itself to be equal to the value within the ‘Email address’ input box. We don’t need to declare this within our controller, which is neat. Also, take a look at the ng-click Angular directives that have been given to each of the submit buttons in our form. Here is where we pass the login and user creation logic to the LoginController, which then delegates to the User factory we created earlier. Superb!

    Likewise, the stylesheet for our login page is quite simple. Worth taking a look at if you’re new to Sass, however. So here it is:

    .wrapper.login
      background-color: grey
    
      h1,
      p
        margin: 0
        margin-bottom: 10px
        color: black
        text-align: center
    
      h1
        text-shadow: 0 2px 0 white
    
      form
        width: 400px
        position: absolute
        top: 50%
        left: 50%
        transform: translate(-50%, -50%)
        padding: 10px
        border: 1px solid black
        border-radius: 15px
        background-color: lightgrey
    
        input
          width: 100%
          float: left
          clear: left
          padding: 10px
          margin-bottom: 10px
          font-size: 25px
    
          &:last-child
            margin: 0
    
          &[type="text"],
          &[type="password"]
            border: 0
    
          &[type="submit"]
            cursor: pointer
    

    Some obvious differences from CSS are that Sass does not use braces (good or bad? I think it’s cleaner and braces are unnecessary for a language like CSS) and also that you can chain selectors using an ampersand, which comes in handy when you want to keep selectors tucked away nice and tidy within their container. Note that ‘&.class‘ and ‘& .class‘ do different things – the second selects a separate element within the parent selector.


10. Brunch and go!

So now we have our application ready to go except that we have not yet compiled the JavaScript and Sass files. To do this we use brunch watch on the command line. Brunch will compile all the files into our public folder… but how? We need to tell it how! Read on…

  • 10.1. Brunch Configuration: Brunch uses a configuration file called config.coffee in the root of our project. Take a look at it below, but for more details read Brunch’s documentation.
    exports.config =
      modules:
        definition: false
        wrapper: false
      paths:
        public: 'public'
        watched: ['angular']
      notifications: true
      files:
        stylesheets:
          joinTo:
            'css/app.css': /^(angular|vendor|bower_components)/
          order:
            before: ['angular/app.sass']
        templates:
          joinTo:
            'js/templates.js': /^angular/
        javascripts:
          joinTo:
            'js/app.js': /^angular/
            'js/vendor.js': /^bower_components/
          order:
            before: ['angular/app.js']
      server:
        path: 'server.js'
        port: 8080
      watcher:
        usePolling: true
      plugins:
        sass:
          mode: 'ruby'
        autoprefixer:
          browsers: ["last 1 version", "> 1%", "ie 8", "ie 7"]
          cascade: false
      minify: true
    
  • 10.2. Go!
    1. Make sure you have a local MongoDB instance running on your machine.
    2. Set your NODE_ENV environment variable to be ‘dev’. More on that here.
    3. Run brunch watch in the root of the web app’s file structure.
    4. Run node run.js in the command line, in the root of the web app’s file structure. (Running the server.js file will do nothing.)
    5. Navigate to http://localhost:8080 in your browser.
    6. Make some changes and watch Brunch refresh and update the web application automatically.

And that concludes this post. Thank you for reading. If you have any questions please post a comment and we will endeavour to reply as soon as possible. If you’re looking for an extra challenge, try implementing some validation logic so that the user must use a valid email address to sign up, or try amending the /api/user route logic so that it doesn’t send the user’s hashed password to the client!

– Sam

[Abi wrote this bit] We are always looking for new developers so if you’ve read this blog, found it helpful and are interested to find out more about what we do, get in touch, we are always up for a pint and a chat in the pub team@outlandish.com

Hiring: We are looking for experienced developers