Install NVM (Node Version Manager) Mac

  1. use brew to install nvm

brew install nvm

  1. make sure you have this line added to your bash_profile file

export NVM_DIR=~/.nvm

source $(brew --prefix nvm)/nvm.sh

  1. install version of node you want

nvm install v4.0.0

  1. use the appropriate node version

nvm use v4.0.0

Express intall empty express application

npm install -g express-generator

This will create a symlink in bin which points to express module in ~/.nvm/versions/v4.0.0/lib/node_modules/ ~/.nvm/bin/express is the symlink created. This is automatically sourced since .nvm/bin is in the path mentioned in .bash_profile or .bashrc dependending on the shell you use

create a empty dir to install express app

mkdir express

install a default express app

express

This will create the folder structure of the app

install express

in the express folder type

npm install

Start a Project

Create empty dir project1

mkdir project1

cd project1

type express –help

express --help

css option stylus

We will use stylus css option so when we create an app using express use

express --css stylus

package.json

package.json has all the dependencies of the project. it is similar to maven

vim package.json

install app using package.json

npm install

npm install will install app using package.json. It will include all depdencies on package.json

launch the application

npm start

localhost 3000

app starts on localhost 3000 do a curl -i (-i is the get response headers). node js and express have an inbuilt server which launches on port 3000 after npm start

curl -i localhost:3000

What does package.json do

package.json has a dependency section

npm install looks at these dependecies and installs them in node_modules. Do not checkin node_modules

stylus files

.styl files are css preprocessors which have code to generate css. node will read these files and generate css files. These files are in the public/stylesheets

routes

routes is a switch board. currently when you start app it will just have the index view

views

views are present in /views folder. It will have a index.jade which extends from layout.jade

app.js

first block is requires. the next instantiates app and next sets config option. app.use is middleware code. Note the app.use(“/”, “routes”) This says routes has the root index, which will render index.js route

JADE

Jade is a templating engine processed on server side. It has syntax built on indentation

STYLUS

stylus is a css language. It can assign variables to to constants

eg link:color = #FFFFFF. a{color: link-color} instead of repeating FFFFF all around code (similar to public static String in java)

check style.styl in project created

BOOTSTRAP

stacked to horizontal(one of the mostly used features)

we can create a row and in the row can have div with classes .col-mid-1 etc. Search stacked to horizontal in bootstrap site

Bootstrap too comes with less and saas version apart from its css version

STATIC FILES

static files are served using the public folder in the project. If I create a file named hello.html in public folder it can be accessed as http://localhost:3000/hello.html. Basically public is root for static files

TEMPLATES OTHER THAN JADE AND STYLUS

moustache

moustace is tamplating engine which uses curly braces {}. One of the main things in moustache is that it can be used with many languager

Handlebars.js

build only for javascript. it is build over moustache and provides server side and client side templating

to use handlebars put express-handlebars in package.json and install it using npm install, then change your views engine in app.js and then make sure you name views with .handlebars extension

Saas

saas is another preprocessor option built with ruby. with epxress you can use it using express --css compass

less

less is another css preprocessor built with javascript

INTRO TO SOME COMMONLY USED JS LIBRARIES

Passport js

  • passport js is used to login user basically to authenticate
  • you can login via facebook, twitter etc
  • we can also use local username and password system

Mongoose js

  • Used to access mongo db

Karma Test Runner

  • Test runner for node js

Mocha js

  • Testing framework for node js*

Chai

  • An assertion library for node*

Sinon js, supertest

  • Sinon js is mocking library, supertest is a helper utility

CREATING APPLICATION

package.json

{
  "name": "project1",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.13.2",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "express": "~4.13.1",
    "jade": "~1.11.0",
    "morgan": "~1.6.1",
    "serve-favicon": "~2.3.0",
    "stylus": "0.42.3",
    "passport": "0.2.*",
    "mongoose": "3.8.*"
  },
  "devDependencies": {
    "chai": "1.10.*",
    "karma": "0.12.*",
    "mocha": "2.0.*",
    "sinon": "1.11.*",
    "supertest": "0.10.*"
  }
}

versioning in node

  • version is specified as “somemodule”: “major:minor:revision”
  • eg “express”: “2.10.3”
  • 2 is major version which signifies incomaptible release change, 10 is minor version which signifies incremental feature release, 3 is revision number which rignifies a bug fix

routes

  • We will not require the users routes so in app js comment out the following
    • var users = require(‘./routes/users’);
    • app.use(‘/users’, users);
    • delete users.js file in routes folder
  • build a contact route
    • create a file contact.js in routes
    • start off with 2 requires to bring in necessary modulesvar express = require('express'); var router = express.Router();
    • now we need to make our contact route and export it since we want to make it public
    router.get('/',function(req,res){ res.send("it worked"); }); module.exports = router
    • note the second arg to get is a anonymous function
    • Even if we write code in routes/{app}.js it dosent work until we make changes in app.js
    app.js changes
    • Where we have the index route import the contact route
    var contacts = require('./routes/contacts')
    • Also include use where we have specified index use
    app.use('/contacts', contacts); supervisor
    • restart the server after changing javascript. To void restarting the server again and again we will use supervisor. supervisor just does a ctrl +c and npm start again after we save a file
    // add supervisor in devdependencies "devDependencies":{ "supervisor": "*" }
    • Then run npm install
    • now supervisor is in your node_modules in bin
    • put this script in the scripts section of package.json
    "scripts": { "start": "node ./node_modules/.bin/supervisor ./bin/www" },
    • then say npm start

create user interface

  • we will use bootstrap to create nice user interfaces for our application
  • in order to use bootstrap we need to import it in style.styl file in public/stylesheets folder
  • put the below 2 lines in “style.styl*
  • also make sure to download the css version of bootstrap and put it in public/stylesheet folder
  • your stylesheet folder should now contain
    • style.styl
    • style.css
    • bootstrap.min.css
    • bootstrap.theme.css
import "bootstrap.min.css"
import "bootstrap.theme.css"

create contacts html for project

  • create a file list.jade in views folder and put in the contact information html
  • contact information html file list.jade (note the pipe character | on line 8. This means continue with the name p tag in jade, if that wasnt there the 2 buttons will be squisshed side by side)

create 3 files in views folder

  • editform.jade
.form-group
  label(for="fullname") Full Name
  input#fullname.form-control(name="fulname", type="text")
.form-group
  label(for="job") Job/position
  input#job.form-control(name="jon", type="text")
.form-group
  label(for="nickname") nickname
  input#nickname.form-control(name="nickname", type="text")
.form-group
  label(for="email") E-mail Address
  input#email.form-control(name="email", type="text")
  • add.jade
extends layout

block content
  .row
    .col-md-6
      h1 Add a contact
      form(role="form", method="post", action="")
        include editform
        button.btn.btn-primary(type="submit") Add user
            
  • edit.jade
extends layout

block content
  .row
    .col-md-6
      h1 Add a contact
      form(role="form", method="post", action="/contacts/1")
        include editform
        button.btn.btn-primary(type="submit") Edit user
    .col-md-6
      h2 Notes
      form(roles="form", method="post", action="/contacts/1")
        dl
          dt Jan 2 2014
          dd Prefers italian food
          dt Jan 19 2014
          dd Just moved into a new office
        .form-group
          label(for="notes")
          textarea#notes.form-control(name="notes")
        button.btn.btn-default(type="submit", value="addnote") Add note
  • note we have followed DRY principle. Do not repeat yourself, by extracting the common in add.jade and edit.jade into editform.jade

** put the new jade templates in contact.js **

  • open contacts.js
  • change res.send to res.render()
  • render takes 2 parameters
    • name of the jade template
    • object that needs to be passed in
  • in short replace the below in contact.js
    • res.send(“it worked”); with res.render(“list”, {});
  • also add a template for add to render the add template

modify contact.js and add the edit and add templates using render

var express = require('express');
var router = express.Router();

router.get('/',function(req,res){
     res.render("list", {});
 });

 router.get('/add', function(req,res){
   res.render('add', {});
 });

 router.route('/contact_id')
  .all(function(req,res,next){
    contact_id = req.params.contact_id;
    next();
  })
  .get(function(req,res){
    res.render('edit', {});
  })
  .post(function(req,res){
    res.send('Post for contact '+ contact_id);
  })
  .put(function(req,res){
    res.send('Put for contact '+ contact_id);
  })



 module.exports = router

add the Contacts button to index.jade

extends layout

block content
  h1= title
  p Welcome to #{title}
  p
    a.btn.btn-default(href="/contacts/") View Contacts

When to use template and when not to (Logic-Less Templates)

  • Logic is code that makes a decision and acts upon it
  • JADE takes dynamic data and converts it into html
  • routes make complicated decisions and chooses what template to show

Dynamic data in jade

  • we dont have a database so we will create in memory database by using javascript objects
  • first install the underscore library . cd into the project directory and execute below
npm install underscore --save

  • You will see that underscore is created in
[email protected] node_modules/underscore
  • –save will install it and automatically add to the package.json as well
  • now in contact.js add the following line
var _ = require('underscore');
  • also include two helper functions
function lookupContact(contact_id){
  return _.find(contacts, function(c){
    return c_id = parseInt(contact_id);
  });
}

function findMaxId() {
  return _.max(contacts, function(contact){
    return contact.id;
  });
}
  • now we want to add a list to show all the contacts
In contact.js
router.get('/',function(req,res){
     res.render("list", {contacts: contacts});
 });
  • method to post a contact should be updated. We will get the last id and then get data from form and post the data
In contact.js
router.post('/', function(req,res){
      var new_contact_id = findMaxId() + 1;
      var new_contact = {
        id: new_contact_id,
        name: req.body.fullname,
        job: req.body.job,
        nickname: req.body.nickname,
        email: req.body.email
      }

      contacts.push(new_contact);

      res.send('New contact created with id: ' + new_contact.id);
      //res.redirect('/contacts/');
 });
  • the get method for contacts is same but in order to be compatible we will add the data section
 router.get('/add', function(req,res){
   res.render('add', {contact:{}});
 });
  • and then we will add lookup_contact method in .all method
  • Every router needs that so we will avoid code duplication and do it only once
router.route('/contact_id')
  .all(function(req,res,next){
    contact_id = req.params.contact_id;
    contact = lookupContact(contact_id);
    next();
  })
  • edit method will have the contact as the data to be passed
 .get(function(req,res){
    res.render('edit', {contact:contact});
  })
  • post method will be to add notes
.post(function(req,res){
  if(!contact.notes){
    contact.notes = [];
  }
  contact.notes.push({
    created: Date(),
    note: req.body.notes
  });
  res.send('Created new note for contact id '+ contact_id);
  //res.redirect('/contact'+contact_id);
})
  • .put method will be used to update the contact
put(function(req,res){
  contact.name = req.body.fullname;
  contact.job = req.body.job;
  contact.nickname = req.body.nickname;
  contact.email = req.body.email

  res.send('Update succeeded for contact id '+ contact_id);
  //res.redirect('/contacts/');
})

changes to JADE file for dynamic content

  • initial jade file
  extend layout

block content
    h1 Your contacts
    p Here is the list of your contacts
    p
      a#delete.btn.btn-default(href="#") Delete
      | 
      a#add.btn.btn-default(href="/contacts/add") Add new
      table.table
        thead
          tr
            th
            th Name
            th Nickname
            th Email
          tbody
            tr
              td
                input(type="checkbox", id="1")
              td
                a(href="/contacts/1") Joe Smith 
                | Plumber
              td Joe
              td
                a(href="mailto:[email protected]") [email protected]
            tr
              td
                input(type="checkbox", id="2")
              td
                a(href="/contacts/2") pratik kale 
                | Carpenter
              td Joe
              td
                a(href="mailto:[email protected]") [email protected]
            tr
              td
                input(type="checkbox", id="3")
              td
                a(href="/contacts/3") litesh sajnani 
                | engineer
              td Joe
              td
                a(href="mailto:[email protected]") [email protected]
                
  • new jade with dynamic content
 extend layout

block content
  h1 Your contacts
  p Here is the list of your contacts
  p
    a#delete.btn.btn-default(href="#") Delete
    | 
    a#add.btn.btn-default(href="/contacts/add") Add new
    table.table
      thead
        tr
          th
          th Name
          th Nickname
          th Email
        tbody
          if !contacts.length
            tr
              td(colspan=4) You should add a contact
          else
            each contact in contacts
              tr
                td
                  input(type="checkbox", id =contact.id)
                td
                  a(href="/contacts/#{contact.id}") #{contact.name}
                  | #{contact.job}
                td #{contact.nickname}
                td
                  a(href="mailto:#{contact.email}") #{contact.email}

Testing

** Full stack testing **

  • Selenium is a popular Full stack testing tool
  • it takes control of the browser and takes control of your application as user would
  • it tests the entire website from css to JS to html to infrastructure to backend to database
  • Its end to end testing from browser to database
  • full stack testing is slow
  • protractor(javascript) and sst(python) can write code to run selenium (we can code tests to invoke selenium)
  • Also selenium will not make it clear where in code the problem lies. eg we might get error on UI but the problem might be network connectivity at backend or just a temp network glitch or a code error ..we dont know
  • Hence we write unit tests

Test tools

  • in package.json check devDependencies we have karma(test runner)mocha(test framework) and chai(assertion library)
  • We also have sinon which is mocking library
  • We have supertest which is a test client to test middleware and routes without starting the node app
  • notice the script section
    • we have a start script.so npm start will just help us to start the app
    • There is no magic it will just execute stuff in ./bin/www and will start the app
  • similarly we need something like test
    • so in script section add the following
    • "test":"./node_modules/.bin/mocha -u tdd"
    • scripts will now look like
    "scripts": {
    "start": "node ./node_modules/.bin/supervisor node ./bin/www",
    "test": "./node_modules/.bin/mocha -u tdd"
    },
  • if we do an npm test now we will get an error since we dont have a folder called test
  • now say npm test you will see a message saying 0 test passing
  • In test folder create a file called demo.js. It dosent matter what its called it should just have a .js extension
  • We will first import chai in demo.js

** First Test **

var chai = require('chai');
var assert = chai.assert;

//test are grouped into suite using describe function
//typically we put suite into a file as same name of module it loads
//so typically we will have 1 describe for a file
//we cannot have a describe inside describe

describe('First test suite', function(){
  x = 0;
  it('Should pass', function() {
    assert.equal(1,x);
  });
});

  • it function takes a label and a function
  • label begins with should keyword to describe what the test should do
  • lets create a beforeEach function in our test suite
  • beforeEach function runs before every test

var chai = require('chai');
var assert = chai.assert;

beforeEach(function(){

x = x+1

})


describe('First test suite', function(){
  x = 0;
  it('Should pass', function() {
    assert.equal(1,x);
  });
});


  • now with above the test should pass
  • lets see another example of beforeEach function
  • The below test should fail since beforeEach is run for every test. so one test passes but other fails
var chai = require('chai');
var assert = chai.assert;

beforeEach(function(){

x = x+1

})


describe('First test suite', function(){
  x = 0;
  it('Should pass', function() {
    assert.equal(1,x);
  });
  
  it('Should also pass', function(){
    assert.equal(1,x);
  });
  
});

  • afterEach function runs after every test
  • lets use after each and fix the above.. we will set x to 0 after each test
var chai = require('chai');
var assert = chai.assert;

beforeEach(function(){

x = x+1

});

afterEach(function(){

x = 0;

});


describe('First test suite', function(){
  x = 0;
  it('Should pass', function() {
    assert.equal(1,x);
  });
  
  it('Should also pass', function(){
    assert.equal(1,x);
  });
  
});

  • Documentation for chai assert library

chaijs assert

  • So this is how it goes
    • We write a module
    • Then we test the module
    • Check if things work as expected

** Testing some real code: test-Jade**

  • This time we will require jade and call a test called jade render
  • We will add a jade template and make the test fail first
    • add a div with word hello
    • check the expected result
var chai = require('chai');
var assert = chai.assert;

var jade = require('jade');

beforeEach(function(){

x = x+1

});

afterEach(function(){

x = 0;

});


describe('First test suite', function(){
  x = 0;
  it('Should pass', function() {
    assert.equal(1,x);
  });

  it('Should also pass', function(){
    assert.equal(1,x);
  });


  it('Jade test', function(){
    var template = "#container hi";
    var expected = '<div id = "container"></div>';
    var result = jade.render(template);
    assert.equal(expected,result);
  });

});

  • In out Project instead of importing jade we will import our module
  • Otherwise the principles are the same

** Testing http requests **

  • We need to require supertest for this
  • We also need to ** require our app ** file
  • We need to use relative path to our module to get the app file ../app

** Test contact and expect 200 ok **


var chai = require('chai');
var assert = chai.assert;

var jade = require('jade');

var request = require('supertest');

var app = require('../app');

beforeEach(function(){

x = x+1

});

afterEach(function(){

x = 0;

});


describe('First test suite', function(){
  x = 0;
  it('Should pass', function() {
    assert.equal(1,x);
  });

  it('Should also pass', function(){
    assert.equal(1,x);
  });


  it('Jade test', function(){
    var template = "#container";
    var expected = '<div id = "container"></div>';
    var result = jade.render(template);
    assert.equal(expected,result);
  });

  it('Supertest test', function(done){
    request(app).get('/contacts')
    .expect(200,done);
  });


});


** note done above us used for asyn testing **

** expect expects a 200 when stuff is done **

Karma client side testing

  • karma loads browser runs test and reports
  • karma is installed in ./node_modules/karma/bin
  • to initialize karma run the below
./node_modules/karma/bin/karma init
  • It will ask me a bunch of questions
  • For which framework to use? press tab till it says Mocha
  • Do you want to use requirejs press no
  • We can capture Chrome automatically
  • next line just press enter since we will only test in chrome..we can also use multiple browsers
  • We need to tell karma where our javascript files are , they are in public/javascripts/**/*.js
  • We will also place some files in
  • so we will also tell karma to include files in browser-tests/**/*.js
  • we currently dont have any files..hit enter on blank line after entering all files
  • Now we have the option to exclude files. You can just press enter to leave this blank
  • Press no -> to run tests automatically if files change
  • to run karma run ./node_modules/karma/bin/karma start –single-run
  • we will make it easy to run karma by adding run configuration in package.json file
  • add karma to the scripts in package.json
"scripts": {
    "start": "node ./node_modules/.bin/supervisor node ./bin/www",
    "test": "./node_modules/.bin/mocha -u tdd"
    "karma": "./node_modules/karma/bin/karma start --single-run"
  },
  

Leave a Reply

Your email address will not be published. Required fields are marked *