Node.js

Workshops

npm init

it creates basic package.json file

name: heroes

name: heroes

version: 1.0.0

name: heroes

version: 1.0.0

description: heroes app

name: heroes

version: 1.0.0

description: heroes app

entry point: index.js

name: heroes

version: 1.0.0

description: heroes app

entry point: index.js

test command: [ENTER]

name: heroes

version: 1.0.0

description: heroes app

entry point: index.js

test command: [ENTER]

git repository: [ENTER]

name: heroes

version: 1.0.0

description: heroes app

entry point: index.js

test command: [ENTER]

git repository: [ENTER]

keywords: [ENTER]

name: heroes

version: 1.0.0

description: heroes app

entry point: index.js

test command: [ENTER]

git repository: [ENTER]

keywords: [ENTER]

license: [ENTER]

Is it ok?: [ENTER]

Lets install our first module!

npm install express --save

{
  "name": "node-workshop",
  "version": "1.0.0",
  "description": "heroes app",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "ssuperczynski <ss@espeo.eu> (espeo.eu)",
  "license": "ISC",
  "dependencies": {
    "express": "^4.13.4"
  }
}

Our module has been successfully installed

In package.json we pointed index.js

This is our inital stript

So! Lets crete it

touch index.js

So! Lets crete it

touch index.js

Open index.js

var express = require('express');
var app = express();

Open index.js

var express = require('express');
var app = express();

app.get('/', function (req, res) {
    res.send('viewing single offer');
});

Open index.js

var express = require('express');
var app = express();

app.get('/', function (req, res) {
    res.send('viewing single offer');
});

app.listen(3003);

Open browser and go to:

localhost:3003

Open browser and go to:

localhost:3003

var express = require('express');
var app = express();

app.get('/', function (req, res) {
    res.send('viewing single offer');
});

app.listen(3004, function () {
    console.log("server started at 3004 port");
});

But we don't know if server works or not

Much better

but hey, I like colors

 npm install terminal-colors --save

but hey, I like colors

Oh yea!

Lets refactor index.js

var express = require('express');
var app = express();

app.get('/', getOffer);

function getOffer(req, res) {
  res.send('viewing single offer');
}

app.listen(3004, function () {
    console.log("server started at 3004 port");
});

index.js

Lets refactor index.js further 

function getOffer(req, res) {
  res.send('viewing single offer');
}

module.exports = getOffer;

controller/OfferController.js

Lets refactor index.js further 

var express = require('express');
var app = express();
var getOffer = require('./controllers/OfferController');

app.get('/', getOffer);

app.listen(3004);

index.js

Nice!

Looks like we have basic node.js structure

Nice!

Looks like we have basic node.js structure

Lets interact with some database

npm install pg pg-hstore --save

But wait, lets do not install Postgres locally

Lets use Docker!

Lets use Docker!

docker-machine start default

eval $(docker-machine env default)

docker-machine start default

eval $(docker-machine env default)

vi docker-compose.yml

docker-machine start default

eval $(docker-machine env default)

version: '2'
services:
  postgres:
    image: postgres
    ports:
    - "5432:5432"
    environment:
      POSTGRES_PASSWORD: mypassword

vi docker-compose.yml

docker-machine start default

eval $(docker-machine env default)

version: '2'
services:
  postgres:
    image: postgres
    ports:
    - "5432:5432"
    environment:
      POSTGRES_PASSWORD: mypassword

vi docker-compose.yml

docker-compose up

You now what?
We have postgres!!

For postgress we need some config

So, lets use 
sequelize-cli to configure it

Creating out model using

npm install sequelize --save

npm install sequelize-cli --save

node_modules/sequelize-cli/bin/sequelize init

Creating basic schema

node_modules/sequelize-cli/bin/sequelize model:create

--name Offer

--attributes "content:string, title:string"

Update database

node_modules/sequelize-cli/bin/sequelize db:migrate

Lets add more methods to OfferController.js 

function getOffer(req, res) {
  models.Offer.findAll()
  .then((offers) => { res.send(offers); })
  .catch(() => {res.send('Error')});
}

module.exports = getOffer;

controller/OfferController.js

But what 

models.Offer.findAll() 

mean?

But what 

models.Offer.findAll() 

var sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');

var Offer = sequelize.define('Offer', {
  title: {type: sequelize.STRING, allowNull: false},
  content: {type: sequelize.STRING, allowNull: false}
}, {});


export default Offer;

models/offer.js

mean?

But what 

models.Offer.findAll() 

mean?

function createOffer(req, res) {
  models.Offer.create({
      content: req.body.content,
      title: req.body.title
  })
  .then((offer) => {  res.send(offer);  })
  .catch(() => {res.send('Error')});
}


module.exports = getOffer;

controller/OfferController.js

Add new method to index.js 

var express = require('express');
var app = express();
var indexAction = require('./controllers/OfferController');

app.get('/', getOffer);
app.post('/', createOffer);


app.listen(3004);

index.js

Add new method to index.js 

function createOffer(req, res) {
  Offer.create({
      content: req.body.content,
      title: req.body.title
  })
  .then((offer) => {  res.send(offer);  })
  .catch((err) => {res.send(err)});
}

controller/OfferController.js

And send something using Postman

We should get

Alright.
But come back to index.js

What does 

 
require('babel-core/register');

mean?

It means,
we can use ES6
instead of ES5

import express from 'express';
import OffersController from './OffersController';
import UsersController from './UsersController';

let router = express.Router();

router.use('/offers', OffersController);
router.use('/users', UsersController);

module.exports = router;

index.js

import express from 'express';
var express = require('express');
export default router;
module.exports = router;
Offer.create({
    content: req.body.content,
    title: req.body.title
})
.then(offer => res.send(offer))
.catch(err => res.send(err));
Offer.create({
    content: req.body.content,
    title: req.body.title
})
.then(function(offer) { res.send(offer); })
.catch(function(err) { res.send(err)});
.then((user) => {
  return user.get({token: token});
}
.then(user => user.get({token}))

Lets create some authentication

npm install passport passport-http-bearer passport-local bcrypt-nodejs --save

Create User model

node_modules/sequelize-cli/bin/sequelize model:create

--name User

--attributes "email:string, password:string, token:string"

Create Middleware

mkdir middleware

cd middleware

touch passport.js

Register passport strategy

import passport from 'passport';
import local from 'passport-local';

 

passport.use('local', new local.Strategy(options, callback));
export default passport;
import bcrypt from 'bcrypt-nodejs';
import Promise from 'Bluebird';
instanceMethods
: { comparePassword: function (hash) { var userPassword = this.password; var user = this; return new Promise((resolve, reject) => { Promise.promisify(bcrypt.compare)(hash, userPassword) .then((result) => { resolve([result, user]); }, (err) => { reject(err); }); }); } },

Update User model

hooks: {
  beforeCreate: (user, options, cb) => {
    Promise.promisify(bcrypt.genSalt)(10)
      .then((salt) => {
        return Promise.promisify(bcrypt.hash)(user.password, salt, null);
      })
      .then((hash) => {
        user.password = hash;
        cb(null, options);
      });
  }
}

Update User model

import { User } from '../models';
import Promise from 'bluebird';

/// ....

var options = {
  usernameField: 'email',
  passwordField: 'password'
};

var onLogIn = (email, password, done) => {
  User.findOne({where: {email}})
    .then(user =>
      user ?
        user.comparePassword(password) : Promise.reject(new Promise.CancellationError('Wrong email or password'))
    )
    .spread((isMatch, user) =>
      isMatch ?
        done(null, user) : Promise.reject(new Promise.CancellationError('Wrong email or password'))
    )
    .catch(Promise.CancellationError, info => done(null, false, info))
    .catch(err => done(err));
};

 

 

 

Add callback to strategy

//controllers/UsersController.js

import express from 'express';
import {User} from '../models';
import passport from 'passport';

let router = express.Router({mergeParams: true});

 

router.post('/signUp', signUp);
router.post('/logIn', logIn);

export default router;

Create login and register endpoints

function signUp(req, res) {
  User.create({
    email: req.body.email,
    password: req.body.password
  }).then(user => res.send({email: user.email }))
    .catch(err => res.send(err));
}

function logIn(req, res, next) {
  passport.authenticate('local', (err, user, info) => {
    if (err) {
      return res.send(err);
    }
    if (!user) {
      return res.send(info.toString());
    }
    return res.send({user});
  })(req, res, next);
}

Add child router to main router

// controllers/index.js
import UsersController from './UsersController';

router.use('/users', UsersController);
import passport from './middleware/passport';
app.use(passport.initialize());

Register passport as a middleware:

app.js

Bearer token strategy

Add token helper

// helpers/token.js

import crypto from 'crypto';
import Promise from 'bluebird';

export default function generate() {
  return new Promise((resolve, reject) => {
    Promise.promisify(crypto.randomBytes)(48).then((buff) => {
      resolve(buff.toString('hex'));
    }).catch(err => reject(err));
  });
}

Modify local-login strategy

User.findOne({where: {email}})
  .then((user) => {
      return user ? user.comparePassword(password) : Promise.reject(new Promise.CancellationError('Wrong email or password'));
    }
  )
  .spread((isMatch, user) =>
    isMatch ?
      generateToken()
        .then(token => user.update({token}))
        .then(() => done(null, user.token))
      : Promise.reject(new Promise.CancellationError('Wrong email or password'))
  )
  .catch(Promise.CancellationError, info => done(null, false, info))
  .catch(err => done(err));

Modify UsersController

function logIn(req, res, next) {
  passport.authenticate('local', (err, token, info) => {
    if (err) {
      return res.send(err);
    }
    if (!token) {
      return res.send(info.toString());
    }
    return res.send({token});
  })(req, res, next);
}
// middleware/passport.js
import generateToken from '../helpers/token';
import token from 'passport-http-bearer';


passport.use('token', new token.Strategy((token, done) => {
  User.findOne({where: {token}})
    .then(user => user ? done(null, user, {scope: 'all'}) : done(null, false))
    .catch(err => done(err));
}));

 

export var tokenAuthentication = passport.authenticate('token', {session: false});

 

Add token strategy

function singleOffer(req, res) {
  Offer.findById(req.params.id)
    .then(offer => res.send(offer))
    .catch(err => res.send(err));
}
function RespondToOffer(req, res) {
  OfferResponse.create({
      OfferId: req.body.id,
      email: req.body.email,
      content: req.body.content
    })
    .then(response => res.send(response))
    .catch(err => res.send(err));
}

 

router.get('/:id', singleOffer);
router.post('/respond', RespondToOffer);

Add new Offer endpoints

//controllers/OffersController.js

import {tokenAuthentication} from '../middleware/passport';
router.post('/respond', tokenAuthentication, RespondToOffer);

Restrict access to some route

// controllers/UsersController.js

function logOut(req, res) {
  User.findOne({ where: {token: req.user.token}})
    .then((user) => {
      return user.update({token: null});
    })
    .then(() => {
      res.send('Logged out.');
    })
    .catch(err => res.send(err));
}
router.delete('/logOut', tokenAuthentication, logOut);

Add log out endpoint

Testing

deps

npm install --save-dev supertest expect.js mocha

package.json

"scripts": {
  "start": "node bin/index.js",
  "build": "gulp",
  "test": "npm run test-integration",
  "test-integration": "NODE_ENV=test mocha --compilers js:babel-core/register integrationTests "
},

test folder

mkdir integrationTest
cd integrationTest
touch offers.js

integrationTest/offers.js

var request = require('supertest');
var expect = require('expect.js');
var app = require('../bin/index');
var Bluebird = require('bluebird');
var models = require('../models');

describe('offerController', function () {
  beforeEach(function () {
    return Bluebird.all([
      models.Offer.destroy({truncate: true}),
      models.OfferResponse.destroy({truncate: true}),
      models.User.destroy({truncate: true})
    ]);
  });

  it('should have /all endpoint and return with 200', function (done) {
    request(app).get('/offers').expect(200, done);
  });
});

 

it('should login and respond to the offer', function (done) {
  var token = '';

  var newUser = { email: 'julian@espeo.pl', password: 'test' };

  models.User.create(newUser).then(() => {
    request(app).post('/users/logIn')
      .type('form')
      .send(newUser)
      .expect(200, (err, result) => {
        token = result.body.token;
        createSomeOffer();
      });
  });

  function createSomeOffer() {
    return request(app).post('/offers/')
      .type('form')
      .send({content: 'you have to code in XML', title: 'some programmer'})
      .expect(200, respondToOffer);
  }

  ...
function respondToOffer(err, result) {
    if (err) done(err);
    request(app).post('/offers/respond')
      .set('Authorization', 'Bearer ' + token)
      .type('form')
      .send({id: result.body.id, email: 'julian@espeo.pl', content: 'i want this job', password: 'test'})
      .expect(200, onResponseCreate);
  }

  function onResponseCreate() {
    request(app).get('/offers').expect(200, function (err, result) {
      if (err) done(err);
      expect(result.body[0].OfferResponses[0].email).to.be('julian@espeo.pl');
      expect(result.body[0].OfferResponses[0].content).to.be('i want this job');
      done();
    });
  }
});

Frontend

git checkout newerstuff frontend gulpfile.js

Serving static files

import path from 'path';
app
  .use(bodyParser.urlencoded({extended: true}))
  .use(bodyParser.json())
  .use(passport.initialize())
  .use('/', router)
  .use('/static', express.static('static'));

Serve index.html

// controllers/index.js

router.get('/', (req, res) => res.sendFile(path.join(__dirname + '/../frontend/index.html')));

Add some logging stuff

npm install --save morgan winston

Log requests

import morgan from 'morgan';
import fs from 'fs';

 

var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'});

 

app
  .use(morgan('dev', {stream: accessLogStream}))
  .use(morgan('dev'))
Made with Slides.com