airline


node+express+mongodb+jade


Start an airline app
$ express airline
$ cd airline && npm install
$ npm start                 # will cause the package.json start commandline to be run
from app.js remove 
var users = require('./routes/users');
app.use('/users', users);
$ rm routes/users.js
$ rm views/*
$ rm -rf public/*        # delete all images or assets
$ cp Downloads/Ex_Files_Node_JS_EssT/Exercise Files/chap05/02/flight    # copy the flight module we made (has index.js and package.json)
$ vim routes/index.js and      $ vim app.js
 index.js app.js 
 var express = require('express');
var router = express.Router();

var flight = require('../flight')

var flight1 = flight({
    number: 1,
    origin: 'LAX',
    destination: 'DCA',
    departs: '9AM',
    arrives: '4PM'
});

var flight2 = flight({
    number: 2,
    origin: 'LAX',
    destination: 'PDX',
    departs: '10AM',
    arrives: '12PM'
});

module.exports = (function() {
    'use strict';
    var router = express.Router();

    router.get('/flight1', function(req, res) {
        res.json(flight1.getInformation());
    });

    router.get('/flight2', function(req, res) {
        res.json(flight2.getInformation());
    });

    return router;    
})();
 var express = require('express');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');

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

var app = express();
console.log('hello')

app.use(logger('dev'));

app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// 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
        });
    });
}

module.exports = app;
 Factory pattern is another way to by pass module single instances to create multiple objects  with this you only get to make one instance of the module and if you declare it again the same variables are going to be there. only variables inside an object are going to be separate. 

index.js
var Flight = function () {
    this.data = {
        number: null,
        origin: null,
        destination: null,
        departs: null,
        arrives: null,
        actualDepart: null,
        actualArrive: null
    };

    this.fill = function (info) {
        for(var prop in this.data) {
            if(this.data[prop] !== 'undefined') {
                this.data[prop] = info[prop];
            }
        }
    };

    this.triggerDepart = function () {
        this.data.actualDepart = Date.now();
    };

    this.triggerArrive = function () {
        this.data.actualArrive = Date.now();
    };

    this.getInformation = function () {
        return this.data;
    };
};

module.exports = function (info) {
    var instance = new Flight();  // take on the function as a constructor to create a new objectThe above function is an object constructor. Once you have an object constructor, you can create new objects of the same type
    instance.fill(info);

    return instance;
};


app.js
var flight = require('./flight');  //export works as a wrapper for the constructor to add arguments to it

var pdxlax = {
    number: 847,
    origin: 'PDX',
    destination: 'LAX'
};

var pl = flight(pdxlax);


Handle more data (data as an associative array in its own module). app.js remained unchanged.
 data/index.js index.js
 module.exports = {
    "13": {
        "number": 13,
        "origin": "MKE",
        "destination": "LAX",
        "departs": "10:00 AM",
        "arrives": "12:00 PM"
    },
    "18": {
        "number": 18,
        "origin": "LAX",
        "destination": "PHX",
        "departs": "9:00 AM",
        "arrives": "9:30 AM"
    },
    "33": {
        "number": 33,
        "origin": "LAX",
        "destination": "DEN",
        "departs": "5:00 PM",
        "arrives": "8:30 PM"
    },"130": {
        "number": 130,
        "origin": "JFK",
        "destination": "LAX",
        "departs": "7:00 AM",
        "arrives": "9:00 AM"
    }
}
var express = require('express');
var router = express.Router();

var flight = require('../flight')

// flight objects
var flightData = require('../data')

for(var number in flightData) {
    flightData[number] = flight(flightData[number]);
}

module.exports = (function() {
    'use strict';
    var router = express.Router();

    router.get('/flight/:number', function(req, res) {
        var i = req.params.number //req.param('number')

        var currentFlight = flightData[i]

        if(typeof currentFlight == 'undefined'){
            res.status(404).json({status:'error'})
        }else{
            res.json(currentFlight.getInformation());
        }
    });
    
    return router;    
})();

Update Data
 index.js
 var express = require('express');
var router = express.Router();

var flight = require('../flight')

// flight objects
var flightData = require('../data')

for(var number in flightData) {
    flightData[number] = flight(flightData[number]);
}

module.exports = (function() {
    'use strict';
    var router = express.Router();

    router.get('/flight/:number', function(req, res) {
        var i = req.params.number //req.param('number')

        var currentFlight = flightData[i]

        if(typeof currentFlight == 'undefined'){
            res.status(404).json({status:'error'})
        }else{
            res.json(currentFlight.getInformation());
        }
    });

    router.put('/flight/:number/arrived', function(req, res) {
        var i = req.params.number //req.param('number')
        var currentFlight = flightData[i]
        if(typeof currentFlight == 'undefined'){
            res.status(404).json({status:'error'})
        }else{
               currentFlight.triggerArrive()
             res.json(currentFlight.getInformation());
        }
    });

    
    return router;    
})();




postman lets you create http requests instead fo using curl , etc. 

Routing different HTTP verbs in Express is as easy as using different methods. As you build out an API, using the appropriate verbs will help other programmers know how to use your service. Routing different HTTP verbs in Express is as easy as using different methods.


Middleware
When you need to make a change to every request reaching your express application, write middleware. Just be sure to call the next function, otherwise other middleware will never run. 

** to not send that we are running express. 

In app.js before app.use('/',...) have following (middle wares get run one after another, hence the next function)
app.use(function(req,res,next){
    res.set('X-Powered-By', 'Flight Tracker');
    next();
})

HTML Generation with Jade and Styling

download bootstrap and ./dist/css/bootstrap.min.css and copy it to ./public
Jade
$ touch views/layout.jade
$ vim views/layout.jade
doctype 5
html
    head
        title= title
        link(rel="stylesheet", href="bootstrap.min.css")
    body
        div.container
            block content
            //-  A block is going to allow us to define a placeholder where more content can appear. I'm going to name this block, content.
            
$ touch list.jade
$ vim list.jade
extends layout
//- it's going to bring in the layout.jade, and it's going to use it here in list. The next thing I'm going to do is define that content block.

block content
    // I'm going to define what's going to go into that placeholder.
    h1= title
    ul
        each flight, index in flights
            //-  The index becomes the object property name, while flight becomes the object property value.
            //-  This hyphen is going to let me write some raw JavaScript right here in the Jade file. 
            - flight = flight.getInformation()
            li= flight.number + ': ' + flight.origin + '-' + flight.destination
 


app.js     
app.set('view engine', 'jade');
index.js
router.get('/flight/list2', function(req, res) {
                console.log('HERE:---------- /list2')

        res.render('list', {
            title:'All Flights', 
            flights: flightData
        })  //name of jade file
        res.json({})
    });

Add error.jade to views directory
extends layout

block content
  h1= message
  h2= error.status
  pre #{error.stack}

==============================================================
return list of flights as json
router.get('/flight/list/json', function(req, res) {
        var flightsArr = []
        for(var number in flightData){
            flightsArr.push(flightData[number].getInformation());  //in json numbers cant be used as property names. just give core data no wrappers etc.
        }
        res.json(flightsArr)  //name of jade file
    });


after all path are handled in app.js
    app.get('/*', function(req, res) {
        res.status(404).send('unknown path was provided');
    });




Unit Testing

$ cp /Ex_Files_Node_JS_EssT/Exercise Files/chap06/03/data .     # copy test data module which only contains 3 flight information for testing
$ mkdir helpers
$ vim app.js

only as development phase modules not production
$ npm install --save-dev mocha   
Mocha is a unit testing framework, that I'm fond of. It allows you to find a suite of tests you want to run, then provides a done function you can call with each individual test is done. 
$ npm install --save-dev should
The module Should extends the native object prototype with functions for asserting values. Normally, extending the native object prototype is a dangerous thing to do, since it effects the entire JavaScript run time environment. But since the change is only going to last during the lifetime of the unit test, it's not as much of a concern to me
$ npm install --save-dev supertest
lets you simulate HTTP requests to your express application. What's nice about it is that you don't have to fire up the application on the specific server port. Supertest just tests the routes you've defined in your application

To start a suite of unit tests in Mocha, you use the describe function. The first argument you pass into describe is a label for this unit test suite The second argument you pass into describe is an anonymous function. Within this function, is where you write your individual unit tests.
To write a test, you call the it function. The first argument you pass is a label describing what it should do. So I'm going to say it should pass. And then the second argument you pass in is a function. That function is provided with one argument called done. So I'm going to call that done function right away.

$ sudo npm install -g mocha
$ mkdir test
$ cd test
$ vim test.js
describe('flights', function(){
    it('should pass', function(done){
        done()
    })

    it('should not pass', function(done){
        throw 'don\'t pass'
        done()
    })
})

Use some test data
$ cp /Ex_Files_Node_JS_EssT/Exercise Files/chap06/03/data  .

Now wrap code such that it takes data as parameter so that we can use it both with test and training data set.
define data in www.js
pass it to app.js
from app.js pass it to index.js

www.js
var flightData = require('../data')
var app = require('../app')(flightData);

app.js
module.exports = function(flightData){
     ...
    var routes = require('./routes/index')(flightData);

     ...

      return app
}

index.js    remove original module.exports and cut and paste everything to below
module.exports = function(flightData){



}


The data and the server have now been decoupled from the application.

test.js
          var flightsData = require('./data')   // test data
          var app = require('../app.js')(flightsData);
it('should return valid flight data for flight 13', function(done){
        supertest(app)
            .get('/flights/flight/13')
            .expect(200)
            .end(function (err, res){ //takes everything that we've set up in this chain and sends it through supertest and then collects the response.
                // assert that we actually got what we want
                res.status.should.equal(200);
                //res.should.have.status(400);
                done(); //done is only called when our assertion is satisfied

            }); 
    })
    
    it('should return an error for an invalid flight', function (done) {
        supertest(app)
        .get('/flights/flight/999999')
        .expect(404)
        .end(function (err, res) {
            res.status.should.equal(404);
            done();
        });

    });

    it('should mark a flight as arrived', function (done) {
        supertest(app)
            .put('/flights/flight/18/arrived')
            .expect(200)
            .end(function (err, res) {
                res.status.should.equal(200);
                res.body.status.should.equal('done');

                supertest(app)
                    .get('/flights/flight/18')
                    .expect(200)
                    .end(function (err, res) {
                        res.status.should.equal(200);
                        res.body.actualArrived.should.not.equal(undefined);

                        done();
                    });
            });
    });







mongodb

download mongodb and extract
$ cd mongodb-osx-x86_64-2.6.5/
$ mkdir -p ./data/db
$ cd bin
$ ./mongod --dbpath ../data/db  # to start it
$ ./mongo   # to connect to it
> use flights                       to create flights datasbe

$ cd myproject/airline
$ npm install --save mongoose
$ touch db.js
$ vim db.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1/flights', function(err){
    if(err){console.log('error connecting to mongodb')}
});
module.exports = mongoose.connection;
$ vim www.js
var db = require('../db');
$ mkdir schemas
$ cd schemas
$ touch flights.js
$ vim flights.js
var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var flightSchema = new Schema({
    number: Number,
    origin: String,
    destination: String,
    departs: String,
    arrives: String,
    actualDepart: Number,
    actualArrive: Number
});

// Mongoose Model definition
var Flight = mongoose.model('flight', flightSchema);

module.exports = Flight;

$ vim ../routes/index.js
            var record = new FlightSchema(currentFlight.getInformation())//FlightSchema.create(currentFlight.getInformation())

            record.save(function(err){
                if(err){
                    console.log(err);
                    res.status(500).json({status:'failiure'})
                } else{
                    res.status(200).json({status:'success'})
                }
            })

List arrivals
    router.get('/flight/arrivals', function(req, res) {
    //    res.json(FlightSchema.find());
        FlightSchema.find()
            .setOptions({sort:'actualArrive'})
            .exec(function(err, arrivals){
                if(err){
                    console.log(err)
                    res.status(500).json({status:'failiure'})
                } else{
                    console.log(arrivals)
                    res.render('arrivals', {title:'Arrivals', arrivals:arrivals})
                }
        })
    });

$ touch views/arrivals.jade
$ vim views/arrivals.jade
extends layout

block content
        h1= title
        ul
        each flight, index in arrivals
            - landed = new Date(flight.actualArrive)
            li= flight.number + ': ' + flight.origin + '-' + flight.destination + '-'+landed






Persisting Sessions
$ npm install --save connect-mongo
$ vim app.js
    var cookieParser = require('cookie-parser')
    var bodyParser = require('body-parser')
    var session=require('express-session')
    var MongoStore = require('connect-mongo')(session)

    app.use(cookieParser());
    app.use(session({
        secret:'keyboard cat', // The secret is going to be used to encrypt the session information.
        resave:true,             //https://github.com/expressjs/session#options
        saveUninitialized:true,  //https://github.com/expressjs/session#options
        store: new MongoStore({'db': 'flights_sessions'})  //can be separated to a settings file to make sure it is a parameter for 
    }))

    app.use(bodyParser.json())

also add db as another parameter to this whole inclusive function

$ npm install --save express-session
$ npm install --save cookie-parser
$ npm install --save body-parser

www.js
pass db to app 
var app = require('../app')(flightData, db);

index.js
/flight/:number
req.session.lastNumber =number;
/flight/arrivals
lastNumber:req.session.lastNumber
arrivals.jade
p= 'last number:   ' + lastNumber



Login
$ npm install --save passport
$ npm install --save passport-local
$ cp Ex_Files_Node_JS_EssT/Exercise Files/chap07/05/snippets/auth.js .
$ vim app.js
var passport= require('./auth')

 //just above body parser use passport as middleware 
 app.use(passport.initialize());
 app.use(passport.session); //tell passport to use sessions in express

$ vim index.js
    router.get('/login', function(req, res) {
        res.render('login', {title:'Log in'})
    });

    router.post('/login', passport.authenticate('local', {
        //this object defines 1) two redirect routes: failiure redirect 2)
        failiureRedirect:'/login',
        successRedirect:'/user'
    }));

    router.get('/user', function(req, res) {
        if(req.session.passport.user === undefined){
            res.redirect('/login');
        }else{
            res.render('user', {title:'Welcome!', user:req.user})
        }
    });

$ cp Ex_Files_Node_JS_EssT/Exercise Files/chap07/05/snippets/{login,user}.jade .




node REPEL  (commandline toolkit)

$ node
> global   // shows every variable available in global scope
> require
> os = require('os')
> os.type()
...

custom REPEL
$ vim myRPEL.js
var repl = require('repl');
repl.start({prompt:'prompt> '})
$ node myRPEL.js
prompt>


Add REPEL to context of the above application
$ vim ./bin/www.js
var repl = require('repl');
var prompt = repl.start({prompt:'flights> '})

prompt.context.data = flightData;
$ npm start
flights> data
flights> data[577]
flights> data[577].triggerArrive()
flights> data[577]

Taking parameters from commandline node
$ console.log(process.argv)  //printout commandline arguments
$ npm instal --save optimist
$ vim ./bin/www
var argv = require('optimist').argv

console.log(argv) //provides parameters as json

Emmit events and attach listeners
$ vim ./routes/index.js
Emmiter  = require('events').EvenEmitter;

var flightEmitter = new Emitter();
flightEmitter.on('arrival', function(flight){  //move db logic to here for the listener we have already sent done so only log to console if error occured
            var record = new FlightSchema(flight.getInformation())
            record.save(function(err){
                if(err){
                    console.log(err);
                } 
            })
})
flightEmitter.on('arrival', function(flight){  //move db logic to here for the listener we have already sent done so only log to console if error occured
    console.log(flight.data.number)
})



// in the arrived route after trigger arrive 
flightEmmiter.emit('arrival', flights[number]);  //event type and data
res.json({status:'success'});




























Comments