Twitter like app

Introduction

Members - Kshitij Kishore 2014052, Sambhav Malhotra 2014158, Vedaint 2014117

Twitter is used for tweeting and sharing information. Our website/web app will allow the users to share the information in text form similar to that of twitter. Users who are not signed in will just see the tweets but they cannot tweet anything unless they are not registered. Obviously, our project uses both front end and back end elements.

The front end uses HTML/CSS and AngularJS. We used Bootstrap framework for CSS to make our content readable and prettier. AngularJS is a front end MVC framework designed by Google. AngularJS is useful for creating partials and routes for GET and POST requests.

The back end side consists of Nodejs, ExpressJS and MongoDB.

We have 3 members in our group and all of us had some beginner to intermediate knowledge of Javascript. So to utilize this for making a web app we watched tutorials and divided our work into three parts equally.
1. AngularJS and HTML/CSS - Samhav Malhotra
2. MongoDB - Vedaint
3. Nodejs -Kshitij Kishore

  1. Nodejs is an asynchronous framework and it's runtime is built on Chrome's V8 JavaScript engine.
  2. ExpressJS is a Node's most popular module and it main purpose on our project is routing and generating some code for the script that will load our server.
  3. MongoDB is one of the NoSQL database and it is document oriented. The knowledge of JSON here is pretty useful because each document can be written in a JSON format.

First we should know how our web page is going to look like. The top header of the main page will contain the the title and the link to partials for Login and Register. We shall look for this later.

1
2
3
4

Part 1 - AngularJS and HTML

AngularJS

We shall know how AngularJS connects with HTML. The other advantage of using AngularJS is that we don't need templating engines for HTML.
To connect our app with AngularJS, look at line 4, it directly loads the AngularJS code. Leave the rest of the code for later understanding.

<html>
    <head>
        <title>Tweet</title>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.js"></script>
        <script src="javascripts/App.js"></script>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="stylesheets/style.css">
    </head>
    <body ng-app="App">
        <div id='main' class="container">
            <nav class="navbar-fluid navbar-default navbar-fixed-top">
                <div class="container">
                        <a class="navbar-brand" href="#"> Tweet! </a>
                        <p class="navbar-text"> Learn the Web developing stack by building this tiny app</p>
                        <p class="navbar-right navbar-text" ng-hide="authenticated"><a href="#/login">Login</a> or <a href="#/register">Register</a></p>
                        <p class="navbar-right navbar-text" ng-show="authenticated"><a href="#" ng-click="signout()">Logout</a></p>
                        <p class="navbar-right navbar-text" ng-show="authenticated">Signed in as {{current_user}}</p>        
                </div>
            </nav>
            <div class="col-md-offset-2 col-md-8">
                <div ng-view>
                </div>
            </div>
        </div>
    </body>
</html>

Models and view in AngularJS

Create a folder in the directory of the main HTML file. Name that folder js or javascripts and create a javascript file named App.js
Now this is the file where the angular code will be written. First we will name our module and load some dependencies and write the controller which is the main functionality or the logic.

var app = angular.module('App', []);
 
app.controller('mainController', function($scope){
  $scope.posts = [];
  $scope.newPost = {created_by: '', text: '', created_at: ''};
 
  $scope.post = function(){
    $scope.newPost.created_at = Date.now();
    $scope.posts.push($scope.newPost);
    $scope.newPost = {created_by: '', text: '', created_at: ''};
  };
});

The module named App will define the logic and the controller will be used for creating and deleting posts.
To use the module in the HTML file we use a directive 'ng-app' inside <body> or a <div> in the code below. In line 8 we loaded our module using the ng-app directive which is named "App". Then we can use our controller using the ng-controller directive and use that for creating and displaying tweets.

<!doctype html>
<html>
  <head>
    <title>Tweet</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
    <script src="javascripts/App.js"></script>
  </head>
  <body ng-app="App">
    <div id='main' ng-controller="mainController">
    </div>
  </body>
</html>

Scope variable

Look at the angular code above. Now that the two are connected, we can create objects in our mainController for our view to access and display. We're going to invoke a $scope object as we're constructing our mainController. The $scope object is incredibly important, and is the Model part of our Model-View-Controller. It is shared between our controller and our view, and is how we can pass data and even functions between the two.
We'll attach a posts array to our $scope to store all the posts that are created.

We'll also create a newPost on our $scope variable to store information on the post that's being created. We'll save the name of the post's creator, the text content of the post, as well as a time stamp.

We can then write a post function that will add the contents of newPost to our posts array whenever the "Chirp!" button is pressed. We'll also set the time stamp here. Since we want to call this from within the view, we'll attach this to the $scope as well.

We've learned enough of the AngularJS for now. The rest of the part will be covered later.

Part 2 - Nodejs and express.

Nodejs
ExpressJS

Node Package Manager(NPM)

Npm is the package manager for Node. The package manager is responsible for hosting open source modules for the use by Node community. Every application contains a package.json file within its directory which is a manifest of the dependencies required for the application to run.

npm

Getting Started

First off install Nodejs from

www.nodejs.org

NPM will automatically integrate with command line/terminal after we install nodejs.

Generating an express application

To install Express generator globally simply type the command

npm install express-generator -g

(type sudo if you are a linux user).
After you installed express type:
mkdir app
cd app
express

This is automatically generate some code for you and a file named package.json. NPM will read all the dependencies from package.json and place them in node_modules folder. We can add dependencies manually in package.json and then type [[code]]npm install[[/code]] which will install/update any missing dependencies.

Here's how app.js looks.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var passport = require('passport');
//initialize mongoose schemas
require('./models/models');
var index = require('./routes/index');
var api = require('./routes/api');
var authenticate = require('./routes/authenticate')(passport);
var mongoose = require('mongoose');                         //add for Mongo support
mongoose.connect('mongodb://localhost/test-chirp');              //connect to Mongo
var app = express();
 
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
 
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(session({
  secret: 'keyboard cat'
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());
 
app.use('/', index);
app.use('/auth', authenticate);
app.use('/api', api);
 
// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});
 
//// Initialize Passport
var initPassport = require('./passport-init');
initPassport(passport);
 
// error handlers
 
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}
 
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});
 
module.exports = app;

This is the boiler plate code required to run our express application. The require statements allow us to import javascript code. We'll use these to import our API routing code and register them with Express. Let's remove the current require statements to index.js and users.js:

var routes = require('./routes/index');
var users = require('./routes/users');

And replace them with:

var api = require('./routes/api');
//We will uncomment this after implementing authenticate
//var authenticate = require('./routes/authenticate');

To register the route handlers with Express, we'll have to remove the current registrations with index and users:

app.use('/', routes);
app.use('/users', users);

and replace it with

//app.use('/auth', authenticate);
app.use('/api', api);

Implementing a RESTful API

RESTful APIs follow a convention which present resources to the client. In our case a Post is a resource and because of this we will implement a /posts API which will First we'll implement placeholder route handlers for the /posts api within api.js.

Every router begins with a require to express, and using the express Router class. At the end of the router implementation we export this module as the Router to be consumed by the code we added in app.js

var express = require('express');
var router = express.Router();
 
//  Some implementation....
 
module.exports = router;

Now between these two things we need to add the /posts api handlers. The way this works in express is by registering the handler to a specific HTTP method, such as GET, PUT, POST or DELETE. Let's add the handlers for the /posts route:

var express = require('express');
var router = express.Router();
 
//api for all posts
router.route('/posts')
 
    //create a new post
    .post(function(req, res){
 
        //TODO create a new post in the database
        req.send({message:"TODO create a new post in the database"});
    })
 
    .get(function(req, res){
 
        //TODO get all the posts in the database
        req.send({message:"TODO get all the posts in the database"});
    })
 
module.exports = router;

Now that we got our /posts api completed we need to create some apis for individual posts. We can do this by building off of our /posts path and adding an ID specific route for an individual post. We use the ':' notation in our route name which tells Express that that particular part of the route will be treated as a parameter:

//api for all posts
router.route('/posts')
 
    //create a new post
    .post(function(req, res){
 
        //TODO create a new post in the database
        res.send({message:"TODO create a new post in the database"});
    })
 
    .get(function(req, res){
 
        //TODO get all the posts in the database
        res.send({message:"TODO get all the posts in the database"});
    })

Finally our full router implementation looks like this:

var express = require('express');
var router = express.Router();
 
//api for all posts
router.route('/posts')
 
    //create a new post
    .post(function(req, res){
 
        //TODO create a new post in the database
        res.send({message:"TODO create a new post in the database"});
    })
 
    .get(function(req, res){
 
        //TODO get all the posts in the database
        res.send({message:"TODO get all the posts in the database"});
    })
 
//api for a specfic post
router.route('/posts/:id')
 
    //create
    .put(function(req,res){
        return res.send({message:'TODO modify an existing post by using param ' + req.param.id});
    })
 
    .get(function(req,res){
        return res.send({message:'TODO get an existing post by using param ' + req.param.id});
    })
 
    .delete(function(req,res){
        return res.send({message:'TODO delete an existing post by using param ' + req.param.id})
    });
 
module.exports = router;

Start the application by typing

npm install

Testing Your APIs

To test your API's we'll use Advanced Rest Client a Chrome browser application that allows us to test our API's without having to write code. You can also use Postman another great application to do the same.

Open Chrome and either install or open Advanced Rest Client. We will make a request to each API we implemented ensure we get the correct place-holder message:

api

Note that because in app.js we assigned express to use the api router at /api all routes in this file will have the prefix ''/api'

Ensure that you get the correct 'TODO' message for the remaining /api/posts.

To test the /api/posts/:id routes you can simply use any string for the :id part of the path. When we implement the API with MongoDB, we'll use the generated ID's from MongoDB.

Adding the Authentication APIs

Most applications require some type of authentication to provide some basic identity to users. Adding local authentication is quite easy. It involves using browser sessions as semi-permanent data in which users are authenticated and some APIs to allow the creation of users in the database.

Installing the Modules

Passport is the library we will use to handle storing users within HTTP sessions. Express-session is middleware for handling sessions and bcrypt will allow us to store our passwords as hashes as its never a good idea to store passwords. First we have to install a few modules. Navigate to the ./Start folder and execute:

# install express session middleware
npm install express-session --save
# install passport
npm install passport --save
# install passport-local strategy for handling authentication
npm install passport-local --save
# install bcrypt-nodejs for handling creating password hashes
npm install bcrypt-nodejs --save

Express Middleware

Middleware are units of code which have access to both the request response and the next route middleware in line. These code units are executed before the route handler has the opportunity to execute and they can be assigned at the router level or to the entire express application.

You can think of these things as 'middlemen' standing between your client and your route handler to provide some general functionality.

Session Middleware

We will use the express-generator middleware at the application level to handle maintaining sessions. To do this go to app.js and add the following require statement at the top:

var session = require('express-session');

Now change your middleware section to use the session module:

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
//Add this portion to your middleware section
app.use(session({
  secret: 'keyboard cat'
}));
//
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

It's important to note that sometimes the order of the middleware matters. In our case we can place it at the very top.

The session manager uses a secret to maintain sessions. In practice, you should keep this secret value outside of your code repository in an environment variable.

Bootstrapping Passport

To bootstrap passport first we'll require passport in app.js:

var passport = require('passport');

Now, add passport as an application level middleware. We'll add it to the bottom of the middleware chain:

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(session({
  secret: 'keyboard cat'
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());

Initializing Passport

We need to Initialize Passport. Passport has it's own middleware handlers in which you assign at 'Strategy'. We will use a Local strategy since we'll just be using local accounts and not any higher level authentication such as Social authentication via Facebook. You can read more about this on the Passport documentation.

In order to properly initialize passport we need to initialize it with its own middleware which will tell passport how to persist users in our datastore. Next module we'll introduce mongodb a NoSQL document oriented database to store our users. In the mean-time we'll just store the users in memory using a javascript object.

Add a new javascript file within the ./App directory

#create passport-init.js file
touch passport-init.js

Now copy and paste this boiler plate passport initialization code which creates the middleware authentication handlers to the Passport specification and exports it as a module:

var LocalStrategy   = require('passport-local').Strategy;
var bCrypt = require('bcrypt-nodejs');
//temporary data store
var users = {};
module.exports = function(passport){
 
    // Passport needs to be able to serialize and deserialize users to support persistent login sessions
    passport.serializeUser(function(user, done) {
        console.log('serializing user:',user.username);
        return done(null, user.username);
    });
 
    passport.deserializeUser(function(username, done) {
 
        return done('we have not implemented this', false);
 
    });
 
    passport.use('login', new LocalStrategy({
            passReqToCallback : true
        },
        function(req, username, password, done) { 
 
            return done('we have not implemented this', false);
        }
    ));
 
    passport.use('signup', new LocalStrategy({
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, username, password, done) {
 
            return done('we have not implemented this', false);
 
        })
    );
 
    var isValidPassword = function(user, password){
        return bCrypt.compareSync(password, user.password);
    };
    // Generates hash using bCrypt
    var createHash = function(password){
        return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
    };
 
};

The code above registers LocalStrategy instances for the signup and login actions in passport. These functions will be responsible for providing passport the user objects from our data storage. Obviously the code above is not complete.

We also have two utility functions createHash isValidPassword for hashing and checking passwords.

At top near your require statements in passport-init.js add a declaration for the users js object:

var LocalStrategy   = require('passport-local').Strategy;
var bCrypt = require('bcrypt-nodejs');
//temporary data store
var users = {};

Let's implement the signup action first:

passport.use('signup', new LocalStrategy({
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, username, password, done) {
 
            findOrCreateUser = function(){
 
                return done('we have not implemented this', false);
            };
 
            return findOrCreateUser();
        })
    );

The signup handler is pretty self explanatory. All we need to do is check if the username is on the user object we created otherwise we store the username and password as a key entry in the users object:
passport.use('signup', new LocalStrategy({
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, username, password, done) {
 
            if (users[username]){
                console.log('User already exists with username: ' + username);
                return done(null, false);
            }
 
            //store user in memory 
            users[username] = {
                username: username,
                password: createHash(password)
            }
 
            console.log(users[username].username + ' Registration successful');
            return done(null, users[username]);
        })
);

To conform to passports specifications, we must call the done callback function on every exit point of the function.

The login handler is also quite simple. Just check if the user is in the data store and if the password matches the hash stored:

passport.use('login', new LocalStrategy({
            passReqToCallback : true
        },
        function(req, username, password, done) { 
 
            if(users[username]){
                console.log('User Not Found with username '+username);
                return done(null, false);
            }
 
            if(isValidPassword(users[username], password)){
                //sucessfully authenticated
                return done(null, users[username]);
            }
            else{
                console.log('Invalid password '+username);
                return done(null, false)
            }
        }
));

Finally the serialize and deserialize handlers need to be provided a unique ID for each user. We can just use the username because those will be unique:
// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function(user, done) {
    console.log('serializing user:',user.username);
    //return the unique id for the user
    done(null, user.username);
});
 
//Desieralize user will call with the unique id provided by serializeuser
passport.deserializeUser(function(username, done) {
 
    return done(null, users[username]);
 
});

Putting it all together our passport-init.js looks like:

var User = require('./models/models');
var mongoose = require('mongoose');   
var User = mongoose.model('User');
var LocalStrategy   = require('passport-local').Strategy;
var bCrypt = require('bcrypt-nodejs');
users = {};
module.exports = function(passport){
 
    // Passport needs to be able to serialize and deserialize users to support persistent login sessions
    passport.serializeUser(function(user, done) {
        console.log('serializing user:',user.username);
        //return the unique id for the user
        done(null, user.username);
    });
 
    //Desieralize user will call with the unique id provided by serializeuser
    passport.deserializeUser(function(username, done) {
 
        return done(null, users[username]);
 
    });
 
    passport.use('login', new LocalStrategy({
            passReqToCallback : true
        },
        function(req, username, password, done) { 
 
            if(users[username]){
                console.log('User Not Found with username '+username);
                return done(null, false);
            }
 
            if(isValidPassword(users[username], password)){
                //sucessfully authenticated
                return done(null, users[username]);
            }
            else{
                console.log('Invalid password '+username);
                return done(null, false)
            }
        }
    ));
 
    passport.use('signup', new LocalStrategy({
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, username, password, done) {
 
            if (users[username]){
                console.log('User already exists with username: ' + username);
                return done(null, false);
            }
 
            users[username] = {
                username: username,
                password: createHash(password)
            }
 
            console.log(users[username].username + ' Registration successful');
            return done(null, users[username]);
        })
    );
 
    var isValidPassword = function(user, password){
        return bCrypt.compareSync(password, user.password);
    };
    // Generates hash using bCrypt
    var createHash = function(password){
        return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
    };

Finally we have to initialize passport with the authentication strategies we've defined in passport-init.js. Add this segment of code after the middleware section in app.js:

//// Initialize Passport
var initPassport = require('./passport-init');
initPassport(passport);

This will call the passport initialization and allow those functions to be called whenever a user needs to authenticate.

Integrating Passport

Now that we have our strategies implemented we need to integrate it to some authentication routes.

Implementing the Auth Routes

First, let's uncomment the require to authenticate.js require within app.js and pass it the passport module:

var authenticate = require('./routes/authenticate')(passport);

Authenticate.js will be written as a router, similar to api.js except it will expose a function that will take the passport module and return the router:
var express = require('express');
var router = express.Router();
 
module.exports = function(passport){
 
    //sends successful login state back to angular
    router.get('/success', function(req, res){
        res.send({state: 'success', user: req.user ? req.user : null});
    });
 
    //sends failure login state back to angular
    router.get('/failure', function(req, res){
        res.send({state: 'failure', user: null, message: "Invalid username or password"});
    });
 
    //log in
    router.post('/login', passport.authenticate('login', {
        successRedirect: '/auth/success',
        failureRedirect: '/auth/failure'
    }));
 
    //sign up
    router.post('/signup', passport.authenticate('signup', {
        successRedirect: '/auth/success',
        failureRedirect: '/auth/failure'
    }));
 
    //log out
    router.get('/signout', function(req, res) {
        req.logout();
        res.redirect('/');
    });
 
    return router;
 
}

Above we've implemented a /login, /signup and signout route. Passport provides the successRedirect and failureRedirect fields to redirect the client to the correct endpoint after an attempted sign-in.
Notice the req.logout function within the /signout route handler. This is middleware added by the passport library to facilitate logging a user out of their session.

Protecting APIs with Authentication using Middleware

Now that we've added our authentication APIs we can actually create our own middle ware to protect some of our APIs.

For Tweet! we want to allow anyone to read posts, but modifying and creating new posts is exclusively for registered users.
Instead of checking in each of our route handlers we'd like to protect if the user is authenticated, we just have to add add one middleware function that will do it.
Within api.js add this function and middleware registration:

//Used for routes that must be authenticated.
function isAuthenticated (req, res, next) {
    // if user is authenticated in the session, call the next() to call the next request handler 
    // Passport adds this method to request object. A middleware is allowed to add properties to
    // request and response objects
 
    //allow all get request methods
    if(req.method === "GET"){
        return next();
    }
    if (req.isAuthenticated()){
        return next();
    }
 
    // if the user is not authenticated then redirect him to the login page
    return res.redirect('/#login');
};
//Register the authentication middleware
router.use('/posts', isAuthenticated);

Now, all requests that aren't GET (generally, requests that do not make any changes) are allowed. However any POST or PUT requests for either /posts or posts/id will receive a redirect and in our case we'll point the client to the front-end login route.

Conclusion

We've created all the necessary routes in express to service our angular application. In this section we've covered how to get started using node, generating an express application, creating routes, using passport for authentication and using middleware to protect our apis.

In the next section we'll fully implement the API route handlers using MongoDB to store Post and User documents for persistent storage.

Part 3 - MongoDB

MongoDB

Document Oriented Databases

Document Oriented Databases are quite different from tabular datastores in that items are stored as Documents, and data is generally enclosed within a single document. Rather than having tables and rows you have collections and documents in order to reduce the amount of joins you have to make.

Feel free to read more about this in our MongoDB MVA material here.
++MongoDB
MongoDB is a popular open source implementation of a Document Oriented Database. It contains collections and documents. Its easy to get started running MongoDB. After you've installed it go to your installation directory and spin up the local server:

# within /bin folder of your mongodb installation execute:
mkdir data
./mongod

Note some systems may require you to run as sudo.

The 'data' directory is required for mongoldb to store its data. It is also possible to store mongodb data elsewhere by specifying the —dbpath argument.

You'll get some output similar to below which will let you know that the MongoDB server is running:

mongod

Creating Models with Mongoose

Now that we have our database running we should specify exactly what we're going to put into it. MongoDB data sort of looks like JSON objects. With data self enclosing as one unit. These units are called documents and are stored in collections.

We will use mongoose as our Object Data Mapper that will allow us to specify a schema for our Post and User objects. Don't worry MongoDB is flexible and allows us to change our schema at any time. Mongoose jus helps us write it down and enforce it.

First we need to install mongoose:

npm install mongoose --save

Navigate to the Start directory and create a new folder called models and then a new file called models.js:

cd ./Start
mkdir models
cd models
touch models.js

Now open models.js and require mongoose.

var mongoose = require('mongoose');

We will now create two schemas, one for Post and the other for User. Schemas are the way we specify to mongoose what kind of objects it can expect us to place in MongoDB. There is a 1:1 relationship between a Schema and a MongoDB Collection.

First lets add the User schema to this file:

var userSchema = new mongoose.Schema({
    username: String,
    password: String, //hash created from password
    created_at: {type: Date, default: Date.now}
});

This schema defines a User which contains a username and password of type String and a created_at property of type Date.

We also need a post schema to represent our 'Cheeps' entries when a user 'Cheeps' a message:

var postSchema = new mongoose.Schema({
    created_by: { type: Schema.ObjectId, ref: 'User' },
    created_at: {type: Date, default: Date.now},
    text: String
});

The created_by is special type of field which references a User document in the Users collection.

Finally we need to use the schemas to register a User and Post model with mongoose.

mongoose.model('Post', postSchema);
mongoose.model('User', userSchema);

At the end of the requires section of app.js add this bootstrapping code which imports the mongoose module and connects us to the local database:

var mongoose = require('mongoose');                         //add for Mongo support
mongoose.connect('mongodb://localhost/test-chirp');              //connect to Mongo

Implementing the Authentication API to use MongoDB

In Module 3 we used an in-memory object to store the users. This was good as a proof of concept to test the routing layer in Express, however we need users to be persistent even if the server restarts. We can do this by changing the implementation to use MongoDB.

First, at the top of the authenticate.js file add mongoose and grab the Users schema:

var mongoose = require('mongoose');   
var User = mongoose.model('User');
var LocalStrategy   = require('passport-local').Strategy;
var bCrypt = require('bcrypt');
//temporary data store
var users = {};

Also remove the users object as we won't be needing it anymore.

Login

Now lets start with our current login handler:

passport.use('login', new LocalStrategy({
            passReqToCallback : true
        },
        function(req, username, password, done) { 
 
            if(users[username]){
                console.log('User Not Found with username '+username);
                return done(null, false);
            }
 
            if(isValidPassword(users[username], password)){
                //sucessfully authenticated
                return done(null, users[username]);
            }
            else{
                console.log('Invalid password '+username);
                return done(null, false)
            }
        }
    ));

Everywhere we use the users dictionary we need to replace with a call to Mongoose. This is pretty easy since we have access to the Users model. First we'll fetch the user requested by passport:

User.findOne({ 'username' :  username }, 
    function(err, user) {
        // In case of any error, return using the done method
        if (err)
            return done(err);
        // Username does not exist, log the error and redirect back
        if (!user){
            console.log('User Not Found with username '+username);
            return done(null, false);                 
        }
        // User exists but wrong password, log the error 
        if (!isValidPassword(user, password)){
            console.log('Invalid Password');
            return done(null, false); // redirect back to login page
        }
        // User and password both match, return user from done method
        // which will be treated like success
        return done(null, user);
    }
);

The findOne function is a convince function in mongoose (and within the mongodb driver itself) which finds the first element that matches the query.

A query object is provided to the function and is used to instruct MongoDb which document(s) to return. In our case MongoDB will retrieve the first user document which contains the username that matches the username requested by passport, which in turn is the username that the user will enter in the browser to login.

Once we execute, the callback provided by Node.js will provide us either with and error err or the user. If user is null we know that that user doesn't exist in the database. If we did get a user object we can then call the same isValidPassword function to validate the provided password. If this works we can call the done callback and provide the user object returned b MongoDb to passport.
Signup

We can do something similar with the Signup API. Let's look at our original implementation:

passport.use('signup', new LocalStrategy({
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, username, password, done) {
 
            if (users[username]){
                console.log('User already exists with username: ' + username);
                return done(null, false);
            }
 
            //store user in memory 
            users[username] = {
                username: username,
                password: createHash(password)
            }
 
            console.log(users[username].username + ' Registration successful');
            return done(null, users[username]);
        })
    );

Similar to the login mongodb implementation we need to fetch the user using the findOne function. If mongodb returns a user, we know that the user already exists and that we cannot register another user with the same name. Otherwise we create a new User set the username and password and save it to the database:

// find a user in mongo with provided username
User.findOne({ 'username' :  username }, function(err, user) {
    // In case of any error, return using the done method
    if (err){
        console.log('Error in SignUp: '+err);
        return done(err);
    }
    // already exists
    if (user) {
        console.log('User already exists with username: '+username);
        return done(null, false);
    } else {
        // if there is no user, create the user
        var newUser = new User();
 
        // set the user's local credentials
        newUser.username = username;
        newUser.password = createHash(password);
 
        // save the user
        newUser.save(function(err) {
            if (err){
                console.log('Error in Saving user: '+err);  
                throw err;  
            }
            console.log(newUser.username + ' Registration successful');    
            return done(null, newUser);
        });
    }
});

Serialize User

Passport requires that we provide a unique ID which specifies each user in order to serialize them into the session. MongoDB creates a field called _id on every object it creates. We can use this as a strong guarantee of a unique identifier.

Take look at our original in-memory implementation of the serialization handler:

passport.serializeUser(function(user, done) {
        console.log('serializing user:',user.username);
        //return the unique id for the user
        done(null, user.username);
    });

Previously we had just used the username field as the key. Instead we'll use mongodbs _id field instead:

// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function(user, done) {
    console.log('serializing user:',user.username);
    done(null, user._id);
});

Deserialize User

Passport also requires that we return a User given a key. Looking our original implementation we used username:

//Desieralize user will call with the unique id provided by serializeuser
passport.deserializeUser(function(username, done) {
 
    return done(null, users[username]);
 
});

This is easy to do since mongoldb since passport will provide us with the _id field of the user we originally provided. By using the findById we can retrieve it from MongoDB:

passport.deserializeUser(function(id, done) {
    User.findById(id, function(err, user) {
        console.log('deserializing user:',user.username);
        done(err, user);
    });
});

Putting it all together

Putting everything together the full MongoDB implementation of the passport-init.js strategies is:

var mongoose = require('mongoose');   
var User = mongoose.model('User');
var LocalStrategy   = require('passport-local').Strategy;
var bCrypt = require('bcrypt');
 
module.exports = function(passport){
 
    // Passport needs to be able to serialize and deserialize users to support persistent login sessions
    passport.serializeUser(function(user, done) {
        console.log('serializing user:',user.username);
        done(null, user._id);
    });
 
    passport.deserializeUser(function(id, done) {
        User.findById(id, function(err, user) {
            console.log('deserializing user:',user.username);
            done(err, user);
        });
    });
 
    passport.use('login', new LocalStrategy({
            passReqToCallback : true
        },
        function(req, username, password, done) { 
            // check in mongo if a user with username exists or not
            User.findOne({ 'username' :  username }, 
                function(err, user) {
                    // In case of any error, return using the done method
                    if (err)
                        return done(err);
                    // Username does not exist, log the error and redirect back
                    if (!user){
                        console.log('User Not Found with username '+username);
                        return done(null, false);                 
                    }
                    // User exists but wrong password, log the error 
                    if (!isValidPassword(user, password)){
                        console.log('Invalid Password');
                        return done(null, false); // redirect back to login page
                    }
                    // User and password both match, return user from done method
                    // which will be treated like success
                    return done(null, user);
                }
            );
        }
    ));
 
    passport.use('signup', new LocalStrategy({
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, username, password, done) {
 
            // find a user in mongo with provided username
            User.findOne({ 'username' :  username }, function(err, user) {
                // In case of any error, return using the done method
                if (err){
                    console.log('Error in SignUp: '+err);
                    return done(err);
                }
                // already exists
                if (user) {
                    console.log('User already exists with username: '+username);
                    return done(null, false);
                } else {
                    // if there is no user, create the user
                    var newUser = new User();
 
                    // set the user's local credentials
                    newUser.username = username;
                    newUser.password = createHash(password);
 
                    // save the user
                    newUser.save(function(err) {
                        if (err){
                            console.log('Error in Saving user: '+err);  
                            throw err;  
                        }
                        console.log(newUser.username + ' Registration succesful');    
                        return done(null, newUser);
                    });
                }
            });
        })
    );
 
    var isValidPassword = function(user, password){
        return bCrypt.compareSync(password, user.password);
    };
    // Generates hash using bCrypt
    var createHash = function(password){
        return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
    };
 
};

Implementing the Posts APIs with MongoDB

In the previous module we didn't implement the route handlers for the /posts and /posts/:id routes, we simply just put TODO's. Now we'll actually implement them since we have a place to store our Data.

/api/posts

First, lets import mongoose and our Post model:

var mongoose = require( 'mongoose' );
var Post = mongoose.model('Post');

Within /routes/api.js lets implement the .post handler for the /posts api route. First we'll create a new post, fill out the text and created_by fields and save it into mongodb. When the callback is executed we know that the post has been saved and we can respond successfully with the saved post body back to the client:

//creates a new post
    .post(function(req, res){
 
        var post = new Post();
        post.text = req.body.text;
        post.created_by = req.body.created_by;
        post.save(function(err, post) {
            if (err){
                return res.send(500, err);
            }
            return res.json(post);
        });
    })

We'll do the same thing with the GET /posts API. In this case we'll call the find function on the Post collection without specifying a query object. This will return all the posts from the database. We can then simply send those to the client and it will automatically be sent as JSON.

//gets all posts
    .get(function(req, res){
        Post.find(function(err, posts){
            if(err){
                return res.send(500, err);
            }
            return res.send(posts);
        });
    });

/api/posts/:id

To complete this router's implementation we'll have to add the mongodb implementation for post-specific commands. Let's start with the GET /posts/:id route:

router.route('/posts/:id')
    //gets specified post
    .get(function(req, res){
        Post.findById(req.params.id, function(err, post){
            if(err)
                res.send(err);
            res.json(post);
        });
    })

As demonstrated in Module 3, the express routing layer will provide params.id because of the :id notation on the route. If the client calls the api with the mongodb ID findById will retrieve the correct document and return it as a json object.

The PUT method is similar in that we will attempt to retrieve the document by ID. We'll then modify the the existing document with the data provided in the request body and save it back to the database:

//updates specified post
    .put(function(req, res){
        Post.findById(req.params.id, function(err, post){
            if(err)
                res.send(err);
 
            post.created_by = req.body.created_by;
            post.text = req.body.text;
 
            post.save(function(err, post){
                if(err)
                    res.send(err);
 
                res.json(post);
            });
        });
    })

Finally, the DELETE method will simply delete the post out of the database using the remove function in the mongoose model:

//deletes the post
    .delete(function(req, res) {
        Post.remove({
            _id: req.params.id
        }, function(err) {
            if (err)
                res.send(err);
            res.json("deleted :(");
        });
    });

Putting everything together our full api.js implementation looks like:

var express = require('express');
var router = express.Router();
 
//Used for routes that must be authenticated.
function isAuthenticated (req, res, next) {
    // if user is authenticated in the session, call the next() to call the next request handler 
    // Passport adds this method to request object. A middleware is allowed to add properties to
    // request and response objects
 
    //allow all get request methods
    if(req.method === "GET"){
        return next();
    }
    if (req.isAuthenticated()){
        return next();
    }
 
    // if the user is not authenticated then redirect him to the login page
    return res.redirect('/#login');
};
 
//Register the authentication middleware
router.use('/posts', isAuthenticated);
 
router.route('/posts')
    //creates a new post
    .post(function(req, res){
 
        var post = new Post();
        post.text = req.body.text;
        post.created_by = req.body.created_by;
        post.save(function(err, post) {
            if (err){
                return res.send(500, err);
            }
            return res.json(post);
        });
    })
    //gets all posts
    .get(function(req, res){
        Post.find(function(err, posts){
            if(err){
                return res.send(500, err);
            }
            return res.send(posts);
        });
    });
 
//post-specific commands. likely won't be used
router.route('/posts/:id')
    //gets specified post
    .get(function(req, res){
        Post.findById(req.params.id, function(err, post){
            if(err)
                res.send(err);
            res.json(post);
        });
    }) 
    //updates specified post
    .put(function(req, res){
        Post.findById(req.params.id, function(err, post){
            if(err)
                res.send(err);
 
            post.created_by = req.body.created_by;
            post.text = req.body.text;
 
            post.save(function(err, post){
                if(err)
                    res.send(err);
 
                res.json(post);
            });
        });
    })
    //deletes the post
    .delete(function(req, res) {
        Post.remove({
            _id: req.params.id
        }, function(err) {
            if (err)
                res.send(err);
            res.json("deleted :(");
        });
    });
 
module.exports = router;

Testing with the REST Client

As before we can test with the Advanced REST Client Tool. For example after we authenticate we can verify we can create a post:

Final%20API

Finalizing the app and tying it up with AngularJS

Because we're using EJS as our templating engine and it supports plain HTML, we can use our index.html as the default view — changing the extension to index.ejs of course. We're going to move the rest of our files to the /public/ folder yet again, to be served as static assets.

Authentication

When we last left our authController, we left the authentication functions quite blank. Now, let's go through and actually implement them.

Creating authentication variables

First off, we'll need some place to store our authentication state that all of our controllers can access. Each controller only has access to its own $scope, but they all have access to the $rootScope of the module. Let's go ahead and initialize some some authentication variables there. Note that we're also specifying $rootScope as a dependency.

//App.js
var app = angular.module('App', ['ngRoute']).run(function($rootScope) {
  $rootScope.authenticated = false;
  $rootScope.current_user = '';
  };
});
...

Handling authentication responses

When users enter their credentials and log in, we should make request to the /auth/login endpoint we created previously. If it's successful, we should set our authentication status in the $rootScope appropriately (note the $rootScope dependency)and redirect them to our posts stream. If it's not successful, we'll display an error message similar to what we did before. The same should happen for our registration page with /auth/register.

In order to redirect our users on successful authentications, we'll need to use the $location service and direct our app to the path we'd like to go to. In this case, it's just our main view at /.

//chirpApp.js
app.controller('authController', function($scope, $http, $rootScope, $location){
  $scope.user = {username: '', password: ''};
  $scope.error_message = '';
 
  $scope.login = function(){
    $http.post('/auth/login', $scope.user).success(function(data){
      if(data.state == 'success'){
        $rootScope.authenticated = true;
        $rootScope.current_user = data.user.username;
        $location.path('/');
      }
      else{
        $scope.error_message = data.message;
      }
    });
  };
 
  $scope.register = function(){
    $http.post('/auth/signup', $scope.user).success(function(data){
      if(data.state == 'success'){
        $rootScope.authenticated = true;
        $rootScope.current_user = data.user.username;
        $location.path('/');
      }
      else{
        $scope.error_message = data.message;
      }
    });
  };
});

Signing out

If we're signing users in, we'll also need a way to sign users out. I'd like for users to be able to do this straight from the navigation, so we'll need to put this function in the rootScope as well.

//chirpApp.js
var app = angular.module('App', ['ngRoute', 'ngResource']).run(function($http, $rootScope) {
  $rootScope.authenticated = false;
  $rootScope.current_user = '';
 
  $rootScope.signout = function(){
    $http.get('auth/signout');
    $rootScope.authenticated = false;
    $rootScope.current_user = '';
  };
});

Creating authentication-sensitive elements

Now, let's link to this in the navigation header! We'll use the ng-click directive to call the signout function if Logout is clicked. While we're at it, we can use the ng-show directive to check out authenticated status to only display it if a user is authenticated. We can also display the authenticated user with ng-show and current_user. Last but not least, we'll use ng-hide='authenticated' to hide the login and registration links if the user is already logged in.

<!--index.html-->
...
<nav class="navbar-fluid navbar-default navbar-fixed-top">
  <div class="container">
    <a class="navbar-brand" href="#"> Tweet! </a>
    <p class="navbar-text"> Learn the MEAN stack by building this tiny app</p>
    <p class="navbar-right navbar-text" ng-hide="authenticated"><a href="#/login">Login</a> or <a href="#/signup">Register</a></p>
    <p class="navbar-right navbar-text" ng-show="authenticated"><a href="#" ng-click="signout()">Logout</a></p>
    <p class="navbar-right navbar-text" ng-show="authenticated">Signed in as {{current_user}}</p>
  </div>
</nav>
...

Services

Calling our APIs through a Factory Service

We were using just an empty array for posts, but now we have a real backend that we can call out to! Let's start by replacing our empty array with the our actual feed of chirps! We'll create a simple postService factory with a getAll function that calls out to our API to get all the chirps we already have using the $http service. Note that since we're going to use postService in our mainController, we'll need to add it as a dependency.

//App.js
var app = angular.module('App', []);
 
app.controller('mainController', function($scope, postService){
  $scope.posts = [];
  $scope.newPost = {created_by: '', text: '', created_at: ''};
 
  postService.getAll().success(function(data){
    $scope.posts = data;
  });
 
  $scope.post = function(){
    $scope.newPost.created_at = Date.now();
    $scope.posts.push($scope.newPost);
    $scope.newPost = {created_by: '', text: '', created_at: ''};
  };
});
 
app.factory('postService', function($http){
  var baseUrl = "/api/posts";
  var factory = {};
  factory.getAll = function(){
    return $http.get(baseUrl);
  };
  return factory;
});

Using ngResource

Since our API is a fully RESTful one, Angular has a service that we can use instead of having to manually call out to our endpoint with each type of request, called ngResource. This isn't packaged with Angular, so we'll have to go through and include it in our project.

We can then simply use the $resource in our postService factory, pass our endpoint to $resource, and start performing CRUD operations. We can now use the query method to GET all of our posts instead.

<!--index.html-->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-resource.js"></script>
//App.js
var app = angular.module('App', ['ngRoute', 'ngResource']);
 
...
 
app.controller('mainController', function($scope, postService){
  $scope.posts = postService.query();
 
  ...
 
});
 
app.factory('postService', function($resource){
  return $resource('/api/posts/:id');
});

Limiting posts

Now that we have authenticated users, we should only let them post chirps. Let's use our ng-show directive like before to only display the chirp form if a user is logged in. While we're at it, we can also take out the input field for a username, and display the handle of the logged in user, current_user, instead.

<!--main.html-->
<div class="clearfix">
  <form ng-Submit="post()" ng-show="authenticated">
    <h4>{{current_user}} says</h4>
    <textarea required class="form-control" maxlength="200" rows="3" placeholder="Say something" ng-model="newPost.text"></textarea>
    <input class="btn submit-btn pull-right" type="submit" value="Chirp!" />
  </form>
</div>
 
...

We'll then add current_user to newPost before we send it to the backend. We'll use the save function to POST our new chirp to our API. Since we want it to show up in our feed, we'll fetch the updated feed again in its callback function. We'll also reset the current post to be blank again.
//App.js
...
$scope.post = function() {
  $scope.newPost.created_by = $rootScope.current_user;
  $scope.newPost.created_at = Date.now();
  postService.save($scope.newPost, function(){
    $scope.posts = postService.query();
    $scope.newPost = {created_by: '', text: '', created_at: ''};
  });
};
...

We can use postFactory for so much more, whether it's deleting posts or finding a post by its ID. That won't be covered in the scope of this module, but it does show the power of ngResource. You can find out more about it in the AngularJS Docs

Conclusion

Finally launch the app from the CMD by typing npm start and see whether the app works or not.

Final%20Launch

It seems the server has successfully launched.
Make sure the MongoDB server is active on command prompt/terminal.
Type in the address localhost:3000 where 3000 is the host.
The page has successfully loaded and it is fully functional
A single screenshot of the complete work.

everything

Completed Tasks

1. Set up HTML with AngularJS
2. Install Nodejs and other dependencies and design the backend API
3. Install MongoDB and integrate it with the front end GET and POST requests.
4. Tying the app together and launching in on a local server

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License