2.15.2012

Nomination part 7 - connecting to facebook


Version en español


This post is part of the series about creating an app with node.js, express for Facebook




Lets connect to fcebook to get the user information and store in session the tokens for request, we will only save the userid and name in the db.


For this we will use a module called "facebook-j", this module its already installed, so lets add it to our "app.js" at the beginning of the file with the other modules like this:

var fb = require('facebook-js');


And then lets add some routes, first for the login that will send to the facebook page for the user login or if its already logged in he must accept the app in order to let the app get information from the profile

    /**
     * Login page
     */
    app.get('/login', function(req, res){
        log.notice('trying to login:' + new Date());
        res.redirect(fb.getAuthorizeUrl({
            client_id: '264644053569277', //put the client id
            redirect_uri: 'http://nomi-nation.pinguxx.c9.io/auth/fb', //cambiar si es necesario
            scope: 'offline_access,publish_stream,read_stream'
        }));
    });
    /**
     * FB return
    */
    app.get('/auth/fb', function(req, res){
        log.notice('response from fb: ' + new Date());
        fb.getAccessToken('264644053569277', //clientid
            '76ded2bf195073ce7a183a1ef1cd0b8a', //app secret
            req.param('code'),
            'http://nomi-nation.pinguxx.c9.io/auth/fb', //cambiar si es necesario
            function (error, access_token, refresh_token) {
                if (error){
                    log.debug('error getting access token:' + error);
                    throw new Error('Error getting the acccess token');
                }
                log.notice('trying to get the tokens:' + new Date());
                req.session.user = {};
                req.session.user.access_token = access_token;
                req.session.user.access_token_secret = refresh_token;
                fb.apiCall('GET', '/me/', {access_token: req.session.user.access_token}, function(error, response, body){
                    if (error){
                        log.debug('error getting user info:' + error);
                        throw new Error('Error getting user information');
                    }
                    log.notice('getting info from user:' + body.id);
                    req.session.user.name = body.username;
                    req.session.user.id = body.id;
                    res.redirect('/dashboard');
                });
            }
        );
    });


Line 4-5: got the login path and log that someone is trying to access


6: we redirect to whatever Facebook api returns


7: add the client id that we got from Facebook, if you need help creating and app in Facebook let me know in the comments section


8: the "redirect_uri" its where we are going to return once the user has accepted our ap


9: the scope of our app, meaning what things are going to see from the app, we will need publish and read, we may not need to read it but for know lets leave it like that, the offline access is to read the user profile.

15 y 16: path is needed to put where to return and then we log whatever Facebook returns


17-21: we ask an access token to Facebook, we pass the "client_id" and the secrecy key of the app


22-25: if something bad happens lets report it

26-30: logging and storing in session the access token and we make another call to the api but this time to get the user information from "/me", we send the access token that we just got

31-38: check errors again, log and save the name and we redirect to the "/dashboard" path
Bien para manejar la ruta y las próximas rutas de dashboard usaremos otro archivo que le llamaremos dashboard.js en este por el momento solo declararemos la ruta principal


Lets create a file called "dashboard.js" to hold all the paths for the actual app, it will look like this:

/**
 * Rutas dashboard
*/
module.exports = function(app, log){

    /**
     * Dashboard landing
    */
    app.get('/dashboard', function(req, res){
        log.notice('landed on dashboard user: ' +
            req.session.user.id + ' on: ' + new Date() );
        res.render('dashboard', { user: req.session.user });
    });
};


Simple, just log out and send the dashboard template with the user object


lets add this dashboard routes to the "app.js" just like before, add it after index.js

require('./routes/dashboard')(app, log);


OK, our new view will be called "dashboard.jade" and right now we will show only the data that we store from Facebook

.username #{t('dashboard.user_name')}: #{user.name}
.userid #{t('dashboard.user_id')}: #{user.id}


Using our i18next module we add the name and the id, so we just need to update the "translation.json" for all the languages

    "dashboard":{
        "user_name" : "Nombre del usuario",
        "user_id" : "Id del usuario"
    }


And we are set, our app is now connected to Facebook and downloading info from there, in later posts you will see how to get other information

Fork the code in github:


Greetings 

2.14.2012

Nomination part 6 - internationalization



Version en español


This post is part of the series about creating an app with node.js, express for Facebook



Lets add i18 to our app since the beginning so we dont end up with a lof of strings to translate.


To do it so we are going to add a list of strings in a local file that we load to the app, it depends on the language settings in the user browser the file we are going to send.


Lets start adding a new module to our package called "i18next" that will allow us to handle the localization files, our "package.json" will look like this:


{
    "name": "nomi-nation"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.2"
    , "express-messages" : ">= 0.0.2"
    , "facebook-js" : "1.0.1"
    , "jade": ">= 0.0.1"
    , "log" : "1.2.0"
    , "mongoose" : "2.4.8"
    , "vows" : "0.6.1"
    , "zombie" : "0.12.9"
    , "jsdom" : "0.2.10"
    , "i18next" : "0.5.1"
  }
}

And as always lets update the dependencies with "npm install -d"

We need to create a folder called "locals" and inside of it more folders depending on the languages you are going to support in this case will be en-US, es-MX, en, es, those are in their iso name, inside all this sub folders we are going to add a file called "translation.json" same for all folders, the way i18next works is to get the code from the user request for example "en-US", if doesn't find it then will look for "en" if that also doesn't exists it will go to a default route if doesn't exists anywhere then it just returns the string you used as identifier, got it?... our translation file will look like this:

{
    "app": {
        "name": "Nomi-nation",
         "welcome" : "Welcome to $t(app.name)"
    },
    "creator": {
        "name": "MrPix"
    }
}


and in spanish:

{
    "app": {
        "name": "Nomi-nation",
        "welcome" : "Bienvenido a $t(app.name)"
    },
    "creator": {
        "name": "MrPix"
    }
}

Its a simple json file with keys and values, we can do nice things like in line 2, we are adding the app name inside our welcome string so it will get app.name instead of the code.

We have our files so lets code...

In "app.js" around line 8 lets add this line to load the module

i18next = require('i18next')


After our variables we need to initialize our i18next:

i18next.init({
    ns: { namespaces: ['translation'], defaultNs: 'translation'},
    resSetPath: 'locales/__lng__/new.__ns__.json',
    saveMissing: true
});

Here we are telling i18next that the namespace to load is translation which is our file and we add some default, the "resSetPath" helps to set the path dynamically, the "__" indicates a variable and at the end "saveMissing" its just to save all the keys that i18 didn't find in a new file, we shouldn't use this in prod.


In our configure part, lets add a helper for our views so we can use this without problems in jade

app.use(i18next.handle);


And after the configure part, lets initialize the helper:

i18next.registerAppHelper(app)
    .serveClientScript(app)
    .serveDynamicResources(app)
    .serveMissingKeyRoute(app);


Cool we are done in "app.js", lets see the templates. First "layout.jade" will look now like this:

!!! 5
html(lang="en")
  head
    title= t('app.name')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body!= body
  script(src="http://code.jquery.com/jquery-1.7.1.min.js", type="text/javascript")
  script(src='i18next/i18next.js', type='text/javascript')
  script(src='javascripts/script.js', type='text/javascript')

We are updating the title to now get "t('app.name')" which is the key "app" and then the sub-key "name".

Then we are adding "i18next.js" that i18next server this file, this will help us to initialize i18 in the client side.

"index.jade" will look like this:
h1= t('app.name')
p #{t('app.welcome')}
.by by: #{t('creator.name')}

We are loading the strings in different ways, first we are just adding and h1 with the app name and then we are printing as a variable inside p and the div ".by" the welcome msg and the creator string.

OK, lets initialize i18next in the client side, remember that we already have the js file that we add in the layout, lets see the js code to initialize it:

var options = {
    ns: { namespaces: ['translation'], defaultNs: 'translation'},
    useLocalStorage: false,
    resGetPath: 'locales/resources.json?lng=__lng__&ns=__ns__',
    dynamicLoad: true,
    sendMissing: true
};
$.i18n.init(options, function() {
    //TODO: add more text
});


First we are creating an object to add all the options, we pass the namespace like in the server, the flag to use the localstorage in the browser if present, the path to load the files and to send the missing keys, then we just start the functionality with jquery.


To use it everywhere we can just use the function t like this:

$('#appname').text($.t('app.name'));


or

$.t()


or use the jquery function with something like this:

// file with strings
"nav": {
    "home": "home",
    "1": "link1",
    "2": "link2"
}

// the html
<ul class="nav">
    <li class="active"><a href="#" data-i18n="nav.home">home</a></li>
    <li><a href="#" data-i18n="nav.1">link1</a></li>
    <li><a href="#" data-i18n="nav.2">link2</a></li>
</ul>

// traduce all the elements with "data-i18n"
$('.nav').i18n();


We are set, we have our app ready for i18, instead of hardcoding the strings lets use this functionality and we can present our app in several languages


Resources:


By the way, update the testing because we change the title from "Express" to "Nomi-nation"



Fork the code in github:


Greetings 

2.12.2012

Nomination part 5 - testing



Version en español


This post is part of the series about creating an app with node.js, express for Facebook





So lets not code everything without knowing if we are doing things right or what... lets start testing what we do or better yet lets try to do some TDD, or do the test cases before the actual code... for this part we need to install some modules to test, first one is vows which has a nice way to describe the test cases, zombie to browser without a browser :) and jsdom to manipulate the dom once the pages are loaded, "up an atom"...


Lets update our package.json adding vows, zombie and jsdom, it will look like this:

{
    "name": "nomi-nation"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.2"
    , "express-messages" : ">= 0.0.2"
    , "facebook-js" : "1.0.1"
    , "jade": ">= 0.0.1"
    , "log" : "1.2.0"
    , "mongoose" : "2.4.8"
    , "vows" : "0.6.1"
    , "zombie" : "0.12.9"
    , "jsdom" : "0.2.10"
  }
}

And then we install the new dependencies with:

npm install -d

Now that we have our modules, lets create a folder called "test" or whatever and we create 3 files, "main.js", "controller.js" and "routes.js".

Lets start with the controller, lets imagine we have this structure in our db:
3 nomination, test1 owner "1", test2 owner "2" y test3 owner "3"
Easy right, lets start our testing, first the controller code will be like this:

/**
 *
 * Controller testing
 *
*/
var vows = require('vows'),
    assert = require('assert'),
    nominator = require('../controllers/nominator.js');

exports.findN = vows.describe('find nominations').addBatch({
    'when finding where 2 voted' : {
        topic : function(){
            nominator.findVoted(2,this.callback);
        },
        'result its only one nomination' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 1);
        },
        'and its test2' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.equal('test2', docs[0].name);
        }
    },
    'when finding where 1 voted' : {
        topic : function(){
            nominator.findVoted(1,this.callback);
        },
        'result is 3 nominations' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 3);
        },
        'and are test1 2 and 3' : function(err, docs){
            if (err) { console.log(err); return; }
            for (var i=0;i<3;i++){
                assert.equal('test'+(i+1), docs[i].name);
            }
        }
    },
    'when finding where 8 voted' : {
        topic : function(){
            nominator.findVoted(8,this.callback);
        },
        'result is 0' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 0);
        }
    }
}).addBatch({
    'when finding 1 nominations' : {
        topic : function(){
            nominator.findMyNominations(1,this.callback);
        },
        'result its only one nomination' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 1);
        },
        'and its test1' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.equal('test1', docs[0].name);
        }
    },
    'when finding 9 nominations' : {
        topic : function(){
            nominator.findMyNominations(9,this.callback);
        },
        'result is 0' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 0);
        }
    }
}).addBatch({
    'when finding where 8 is nominated' : {
        topic : function(){
            nominator.findNominated(8,this.callback);
        },
        'result its 3 nominations' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 3);
        },
        'and its test1, 2 and 3' : function(err, docs){
            if (err) { console.log(err); return; }
            for (var i=0;i<3;i++){
                assert.equal('test'+(i+1), docs[i].name);
            }
        }
    },
    'when finding where 1 is nominated' : {
        topic : function(){
            nominator.findNominated(1,this.callback);
        },
        'result is 0 ' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 0);
        }
    }
});

Line 1-8: Loading modules and our controller "nominator.js"


10: we export the function "findN" with exports and we start describing the test in this case we are going to name it 'find nominations' and we add a batch, this will hold all our test cases.


11-14: The first test is called 'when finding where 2 voted', eso which means we are going to search for nominations where the user with id "2" already voted, first we create the topic that is like the entrance to the test case where we call the function "findVoted" from the nominator controller, we send the id to search and something important here, we use the local callback "this.callback", this means that whatever the function returns it will go to all the next test we have inside this topic in an async way


15-18: The next test is: 'result its only one nomination', the result expected its only one nomination and to check it first lets make sure we didnt get any error from the callback and then the lenght of the docs returned, since we are searching this always return an array of docs, even if the result is only one, to check the lenght we do "assert.lengthOf" to docs and we compare to 1 that is the lenght we are expecting.


19-22: Next test its called 'and its test2', here we also get the results from "findVoted" and in this case we check that the name of the nomination is "test 2" which is the only one that user 2 has voted, first we check for an error and then we check the name asserting "doc[0].name" to "test2"

...: Well its the same again and again in this file just checking different functions from our nomination controller and checking other things, we just add them to the batch, review them, any questions let me know in the comments


This code is what i use to add users and nominations:

var nomination = {
    'name' : "test3",
    'owner' : "3", //who is the owner of the nomination
    'endDate' : new Date(), //when this nomination is going to end
    'category' : "cat1",
    'sub_cat' : "sub1",
    'active' : true //nomination finished
};
nominator.createNomination(nomination, function(err, doc){
    if (err) { console.log(err); return; }
    var users = [];
    for(var i = 4; i<12; i++ ){
        users.push({
            "_id" : i,
            "name" : i,
            "votes" : 0
        });
    }
    nominator.addUser(doc, users, function(err){
        if (err) {  console.log(err); return; }
        var user = {"_id":9, "name":9, "votes":0 };
        nominator.eraseUser(doc, user, function(err){
            if (err) {  console.log(err); return; }
            nominator.addUser(doc, user, function(err){
                if (err) {  console.log(err); return; }
            });
            nominator.addUser(doc, {"_id":19, "name":19, "votes":0 }, function(err){
                if (err) {  console.log(err); return; }
            });
        });
    });
});

In this case we are creating a nomination and after that we add some users with "adduser" and then we erase some and add more just for fun.

And to add votes we do:

nominator.findMyNominations(1, function(err, doc){
    if (err) { console.log(err); return; }
    for (var i=2;i<5;i++){
        nominator.vote(doc[0], 1, i, function(err, doc){
            if (err) {  console.log(err); return; }
        });
    }
});

We search for userid 1 nomination and we add voters. try to do some testing for this with vows...

Good, lets test our main route "/", zombie to the rescue. Lets add the following code to "routes.js":

/**
 *
 * Test index route
*/
var vows = require('vows'),
    assert = require('assert'),
    zombie = require('zombie'),
    jsdom = require('jsdom');

exports.index = vows.describe('Visiting index').addBatch({
    'when hitting the page': {
        topic: function(){
            zombie.visit('http://localhost:3000',this.callback);
        },
        'we got the page': function(err, browser, status) {
            if (err){
                throw(err.message);
            }
            assert.equal(status, 200);
        },
        'and try to get the title' : {
            topic : function(browser){
                var vow = this;
                jsdom.env({
                    html: browser.response[2],
                    scripts: [
                        'http://code.jquery.com/jquery-1.5.min.js'
                    ],
                    done: vow.callback
                });
            },
            'the title is Express' : function(errors, window) {
                //console.log(errors);
                if (errors){
                    throw(errors);
                }else{
                    var $ = window.$;
                    assert.equal('Express', $('title').html());
                }
            }
        }
    }
});

Line 1-8: loading modules

10: We add our test "visitin index" y we add our batch (set) of testcases

11-14: "when hitting the page" this test will fire zombie and will try to reach our page, as a callback we put our local callback so all the other test cases have the results

15-20: 'we got the page', this page takes the arguments error, browser and the status, first we check for errors and then that we have a 200 response

21-22: 'and try to get the title', here we are adding a new topic in this case we take the response from zombie minus the error and also this one have status but im not adding it

23-30: lets store vows in a var for future reference and we use jsdom, in  this case we are sending the html that we got from zombie, we inject it jquery and we call the local callback for the next test case

32-39: 'the title is Express', this test gets for arguments what jsdom returns that it may be a possible error or the window that holds all the page, its like our browser page, we check for page errors and if everything is good with jquery we check for the title as if you were in the browser.

Cooll, lets create or "main.js" to call our testing

#!/usr/bin/env node
/**
 * Test runner
 *
*/
var controllers = require('./controllers');
controllers.findN.run();

We just got our testing file and we run them, check the documentation for more great stuff

2.10.2012

Nomination part 4 - models


This post is part of the series about creating an app with node.js, express for Facebook


For this part we will use the mongoose module that we already have installed, so no updates to the package.json.

Our model looks more or less to something like this:


Nomination is our main doc that have all the attributes to create a nomination, this one have a list of users, users its a doc that is embedded in nomination, i put it like this so you can see the separation but its not another doc in mongodb but we need to create the schema definition anyway in mongoose.

To add our modules lets create a folder called "models" or whatever in the root path of our app, inside that lets create a file called "nomination.js".

Lets see our code in nomination.js:

function defineModels(mongoose, fn) {
 var Schema = mongoose.Schema,
 Nomination, User;

/**
 *
 * Model: User
 *
 */
 User = new Schema({
 '_id' : String, //fb id
 'name' : String, //user name
 'votes' : Number //number of votes for this user
 });

 /**
 *
 * Model: Nomination
 *
 */
 Nomination = new Schema({
 'name' : String,
 'owner' : String, //who is the owner of the nomination
 'endDate' : Date, //when this nomination is going to end
 'users' : [User], //users added to this nomination
 'voters' : [String], //all the dudes that vote this nomination
 'category' : String,
 'sub_cat' : String,
 'active' : Boolean, //nomination finished
 'erased' : [String] //people erased by facebook id, we wont be able to re-add them
 });

 Nomination.virtual('id')
 .get(function() {
 return this._id.toHexString();
 });

 Nomination.pre('save', function(next) {
 //we could use this later
 next();
 });

 mongoose.model('Nomination', Nomination);

fn();
}

exports.defineModels = defineModels;

Line 1: Creating our main function to export later

2-3: our internal variables and a reference to schema

5-14: user model by "new Schema", with id gathered from fb, name and number of votes this user will have, all of type of String excepto votes

16-31: Our nomination model, this will have name, owner, date to deactivate it, list of nominated users, people that already voted here, category and a subcategory, activation flag and the list of deleted users, in this case we will let mongo to create an id, even if we dont put it, doesnt mean it isn't present in the document, we are just letting mongoose do the work

33-36: A virtual function to get the id, just in case

38-41: a small save middleware, before saving a doc of this type, mongoose will first call this function, for this example im not putting nothing yet, "next()" its to call the next function that in this case will be the actual save.

43: declaring the model

45: we call the callback passed, usually its to make an instance of the model to start using it

48: Exporting the function

Nice, we have our models defined, lets create something to use it, i called it "controller", from there we will handle the mongoose functionality so we are going to expose only whats is necesary.

We need to create a "controllers" folder or whatever name and a file called "nominator.js" that is going to be our controller, this file will look like this:

var mongoose = require('mongoose'),
    models = require('../models/nomination.js'),
    Nomination, db;

models.defineModels(mongoose, function() {
  Nomination = mongoose.model('Nomination');
  db = mongoose.connect('mongodb://nominator:nominat0r@ds029257.mongolab.com:29257/nomi-nation');
});

var NOMINATOR = {};

function findIndexByKeyValue(obj, key, value)
{
    var l = obj.length;
    for (var i = 0; i &lt; l; i++) {
  if (obj[i][key] == value) {
   return i;
  }
 }
 return null;
}

/**
 * Add vote
 * @nomination object
 * @voterId string
 * @userId string
 * @callback function
*/
NOMINATOR.vote = function(nomination, voterId, userId, callback) {
    //TODO: check we dont want more than 10 users per nomination
    var isPresent = -1;
    try {
        isPresent = nomination.voters.indexOf(voterId);
    }
    catch (e){
        isPresent = -1;
    }
    if (isPresent &lt; 0){
        nomination.voters.push(voterId);
    }
    //TODO: change to an actual mongo function
    var index = findIndexByKeyValue(nomination.users, &quot;_id&quot;, userId);
    nomination.users[index].votes += 1;
    nomination.save(callback);
    //Nomination.update({_id :nomination.id}, {users: nomination.users}, callback);
};

/**
 * Add user
 * @nomination object
 * @user object
 * @callback function
*/
NOMINATOR.addUser = function(nomination, user, callback) {
    //TODO: check we dont want more than 10 users per nomination
    var isPresent = nomination.erased.indexOf(user._id);
    if (isPresent &gt;= 0){ callback(new Error('User can\'t be added'), null); return; }
    if (user instanceof Array){
        nomination.users = user;
    }else{
        nomination.users.push(user);
    }
    nomination.save(callback);
    //Nomination.update({_id :nomination.id}, {users: nomination.users}, callback);
};

/**
 * Erase user
 * @nomination object
 * @user object
 * @callback function
*/
NOMINATOR.eraseUser = function(nomination, user, callback) {
    nomination.users.remove(user); //erased from the users list
    nomination.erased.push(user._id); //add to the erased so we dont add them again
    nomination.save(callback);
    //Nomination.update({_id :nomination.id}, {users: nomination.users, erased: nomination.erased}, callback);
};

/**
 * Create nomination
 * @nomination object
 * @callback function
*/
NOMINATOR.createNomination = function(nomination, callback) {
    var myNomination = new Nomination(nomination);
    myNomination.save(callback);
};

/**
 * Erase nomination
 * @nominationid string
 * @callback function
*/
NOMINATOR.eraseNomination = function(nominationid, callback) {
    Nomination.erase({'_id' : nominationid }, callback);
};

/**
 * find nominations the user own
 * @userid fb userid
 * @callback function
 *
*/
NOMINATOR.findMyNominations = function(userId, callback) {
    Nomination.where('owner', userId)
        .where('active', true)
        .run(callback);
};

/**
 * find nominations the user voted
 * @userId fb userid
 * @callback function
 *
*/
NOMINATOR.findVoted = function(userId, callback) {
    Nomination.where('voters').in([userId])
        .where('active', true)
        .run(callback);
};

/**
 * find nominates where the user was nominated
 * @userId fb userid
 * @callback function
 *
*/
NOMINATOR.findNominated = function(userId, callback) {
    Nomination.where('users._id').in([userId])
        .where('active', true)
        .run(callback);
};

// Exporting functions
module.exports = NOMINATOR;

Line 1: loading the mongoose modules

2: loading the models, we only export one function

3: our local variables

5-8: we start using the function we exported in "models.js" and we make an instance of the model "nomination" and we connect to the db

10: Lets create our object that will hold all the functionality to export it at the end

11: A small helper function to look for indexes inside the arrays

23-47: Adding votes, lets first check if the user voted its already in the list, if not to added if yes, to add one vote to this victim nominated, we save the model that function will make this change to go directly to the mongodb, lets call the callback to show the results for whom call us, this callback will have as parameters "error" and "doc"

49-66: Adding users, first lets check the user its not in our list of people deleted so we don't put it again, if so lets throw an error, then lets check if we are getting an array or a single user and add them to the list of users and then we save and call the callback


68-79: Erasing users, first lets erase him for the list of users and then add them to the list of erased, save and call the callback


81-89: Creating nominations, we get as a parameter an object with the attributes needed to create a nomination, we make an instance of it and save it, calling the callback


91-125: Searching, search is easy using query, we use the base model and we keep adding whatever we are looking for, e.g,:  "where('owner', userId)", here we are looking where the nomination owner is the userId that we get as a parameter, then we check that this nomination is active and at the end we run the query with "run(callback)" and send the results to the callback, this will be the same for all the searching just with different parameters


128: Lets export our object with all the functionality 


We are done!!!, we have our models and our controller to handle them in our app, next time we will see how make some testing for our models and routes


The model picture was created using cacoo :) online


Fork the code in github:

Greetings 

2.09.2012

nomination part 3 - app.js and log

Version en español


This post is part of the series about creating an app with node.js, express for Facebook



OK, let me start rewriting a little bit our app.js, which holds all the routes for the app, we are going to separate the routes into diff files so we don't end up with a very long and complicated file, and lets add the log module to start logging some stuff.

Lets begging updating our package.json by adding the log module:

, "log" : "1.2.0"

We end up with:

{
 "name": "nomi-nation"
 , "version": "0.0.1"
 , "private": true
 , "dependencies": {
 "express": "2.5.2"
 , "jade": ">= 0.0.1"
 , "express-messages" : ">= 0.0.2"
 , "facebook-js" : "1.0.1"
 , "mongoose" : "2.4.8"
 , "log" : "1.2.0"
 }
}

and later from our root folder of our app we do:

npm install -d

With this command we install all our dependencies, in this case only the log was new so you will see it in node_modules folder now.

Lets add the vars required to our app.js for logging, this is at the top of the app.js file:

Log = require('log'),
 log = new Log(),
 fs = require('fs');

In the configuration part lets configure our log, depending on the environment, this is the debug part:

log.level = Log.DEBUG;
 log.stream = fs.createWriteStream('logs/dev.log', { flags: 'w' });

for production change the file name and the "log.level" to "fine" or something that works for you.
Lets add a simple log in the route "/":

log.notice('landed on:' + new Date());

Sweet, logging is ready, lets change the routes to some other files, is really simple actually, first we need to create a folder called "routes", you can call it whenever you want, and we will add a file called "index.js" this will hold our main routes, the name of the file is also whatever you want, index file will be something like this:

/**
 * Main routes
 *
*/
module.exports = function(app, log){
 /*
 * GET home page.
 */
 app.get('/', function(req, res){
 log.notice('landed on:' + new Date());
 res.render('index', { title: 'Express' });
 });
};

Well we only have one route now, this will change in later posts, but lets see what we did here:
modle.exports its to tell node that we are exporting the function that hold several routes, in this case only one, and we are expecting the app variable that holds the actual app, and the log variable that holds the reference for our logging needs.

Back to our app.js, lets erase the route "/" that we have there and lets add the routes file/module:

// Routes
require('./routes/index')(app, log);

We are requiring out index file that export the function with the main routes, and we are passing the app and the log.

We dont create another log variable inside index.js because it will then initialize all the logging again.

We are done, right now we are just setting up our environment for later releases

You can see the code in github:

https://github.com/nodejs-mexico/nomi-nation

Greetings 

2.06.2012

nomination part 1-2


This post is part of the series about creating an app with node.js, express for Facebook




Nomination will be an app to nominate your friends about something specific, like "who is the most ugly" and then you can add your Facebook friends to this nomination and start voting them

version en español

First of course you will need to install node and npm, see nodejs.org if you need help, let me know in the comments.

Lets think about the tools we are going to need:

  • Express as our framework for route handling
  • Jade for templates
  • Facebook-js to connect to the social network
  • mongoose as our orm
  • Mongodb, we are going to use mongolab

Lets start installing express globally in our machine and generate the app

npm install express -g
express nomi-nation
cd nomi-nation
ls
app.js
package.json
public
views

In the folder, we can see that we have app.js that is the template to start programming, now lets install our dependencies, this dependencies can be described in "package.json", lets update this file with something like this:

{
    "name": "application-name"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.4.6"
    , "jade": ">= 0.0.1"
  }
}

Wen we have our package.json ready then we do:

npm install -d

This will install all the dependencies included in the package.json

Now lets see our app running:

node app.js

Open up the browser and point it to "localhost:3000" and we will see our initial page

Lets review the script "app.js";


/**
 * Module dependencies.
 */

var express = require('express');

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', function(req, res){
  res.render('index', {
    title: 'Express'
  });
});

app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

Line 6, load express framework with require, this is the format to load the modules

8, creating the server

12-19, specifying configurations, where are our views or templates, the engine to use (can be haml or another sported by express), use router and the static address for our resources (images, js, css, etc)

21-27, ambient specific configurations

31-35, our main route, we have "app" var which is where we created the server, then ".get" which is the http verb, this will respond only to get request, if its post this won't catch it, we should create "post" for that, then the route "/" and the function to handle that route, res is the response we will send to the client and render is to render the template, this time the "index" and a default template, lastly we send the variable title that holds the title of the page

37, we tall app to listen in the port "3000" and we are set the app begins

38, log out the port and the ambient we are working on

For the moment in the public folder we only have a basic stylesheet and everything else is empty

In views folder we have 2 templates, "index.jade" which is our main page and "layout.jade" that is the default layout, layout looks something like this:

!!!
html
 head
   title= title
   link(rel='stylesheet', href='/stylesheets/style.css')
 body!= body


!!! its to specify the html version

"title=" to create the tag title with whatever we send in the variable title and in the end the body will be whatever we put in "index.jade", index looks like this:

h1= title
p Welcome to #{title}

We are creating a header with the title name and a some welcome message with the same title variable, look the difference, the first one will create the tag if the var have something, the second will create the tag and put as a text whatever the title var have, see more examples in the jade github

Next post will start writing some code :)