- use brew to install nvm
brew install nvm
- make sure you have this line added to your bash_profile file
export NVM_DIR=~/.nvm
source $(brew --prefix nvm)/nvm.sh
- install version of node you want
nvm install v4.0.0
- use the appropriate node version
nvm use v4.0.0
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
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 is a templating engine processed on server side. It has syntax built on indentation
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
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 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
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
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
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 modules
var 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
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
extends layout
block content
h1= title
p Welcome to #{title}
p
a.btn.btn-default(href="/contacts/") View Contacts
- 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
- 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
underscore@1.8.3 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/');
})
- 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:joe@gmail.com") joe@gmail.com
tr
td
input(type="checkbox", id="2")
td
a(href="/contacts/2") pratik kale
| Carpenter
td Joe
td
a(href="mailto:joe@gmail.com") pratikkale@gmail.com
tr
td
input(type="checkbox", id="3")
td
a(href="/contacts/3") litesh sajnani
| engineer
td Joe
td
a(href="mailto:joe@gmail.com") liteshsajnani@gmail.com
- 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}
** 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
- 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
- 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 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"
},