ANDRIY DACENKO

SOFTWARE ENGINEER

September 16, 2015

NODE.JS EXTENDED FEATURES

 SOCKETS, MONGO, PASSPORT

JS MENTORING 2015

CONTENT

Static

Dynamic

Immediate

REALTIME

Periodic Polling

Long Polling

Forever Frame

WebSockets

PERIODIC POLLING

CLIENT

SERVER

Time Line

LONG POLLING

CLIENT

SERVER

Time Line

FOREVER FRAME

CLIENT

SERVER

Time Line

WEB SOCKETS

SIMPLE SOCKET

$ npm install socket.io
<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io('http://localhost:8080');
  socket.on('greet', function (data) {
    console.log(data);
    socket.emit('message', 'user', 'hello');
  });
</script>
var io = require('socket.io')(8080);

io.on('connection', function (socket) {
  socket.emit('greet', { 
    hello: 'world'
  });

  socket.on('message', function (from, msg) {
    console.log('message by ', from, ' saying ', msg);
  });

  socket.on('disconnect', function () {
    socket.emit('user disconnected');
  });
});

NAMESPACES

<script>
  var chat = io.connect('http://localhost/chat')
    , news = io.connect('http://localhost/news');
  
  chat.on('connect', function () {
    chat.emit('hi!');
  });
  
  news.on('news', function () {
    news.emit('woot');
  });
</script>
var io = require('socket.io')(8080);
var chat = io
  .of('/chat')
  .on('connection', function (socket) {
    socket.emit('a message', {
        that: 'only'
      , '/chat': 'will get'
    });
    chat.emit('a message', {
        everyone: 'in'
      , '/chat': 'will get'
    });
  });

var news = io
  .of('/news')
  .on('connection', function (socket) {
    socket.emit('item', { news: 'item' });
  });

CALLBACKS

<script>
  var socket = io();
  socket.on('connect', function () {
    socket.emit('call me', 'andrew', function (data) {
      console.log(data); // data will be 'holla'
    });
  });
</script>
var io = require('socket.io')(8080);

io.on('connection', function (socket) {
  socket.on('call me', function (name, fn) {
    fn('holla');
  });
});

BROADCAST & VOLATILE

var io = require('socket.io')(8080);

io.on('connection', function (socket) {
  var tweets = setInterval(function () {
    getCatsTweets(function (tweet) {
      socket.volatile.emit('cats tweet', tweet);
    });
  }, 100);

  socket.on('disconnect', function () {
    clearInterval(tweets);
  });
});
var io = require('socket.io')(8080);

io.on('connection', function (socket) {
  socket.broadcast.emit('new user connected');
});

TASK

  • ​Create simple server

  • Create input field

  • Create list of messages

Estimates: 10 min

MONGODB

Documents

Collections

Queries

DOCUMENTS

{
   "_id" : ObjectId("54c955492b7c8eb21818bd09"),
   "address" : {
      "street" : "2 Avenue",
      "zipcode" : "10075",
      "building" : "1480",
      "coord" : [ -73.9557413, 40.7720266 ],
   },
   "grades" : [
      {
         "date" : ISODate("2015-09-16T00:00:00Z"),
         "grade" : "A",
         "score" : 11
      }
   ]
}

COLLECTIONS

[{ 
    "_id" : ObjectId("55f89665db8b40a199e357fb"), 
    "name" : "Andrew" 
},
{ 
    "_id" : ObjectId("55f8966bdb8b40a199e357fc"), 
    "name" : "John" 
}]

MONGO SHELL

$ mongo
MongoDB shell version: 2.6.7
connecting to: test
>db
test
>use other
switched to db other
>load('show_databases.js')
{
    "databases" : [{
        "name" : "admin",
        "sizeOnDisk" : 1,
        "empty" : true
    },{
        "name" : "test",
        "sizeOnDisk" : 1,
        "empty" : true
    }],
    "totalSize" : 83886080,
    "ok" : 1
}

SCRIPTING

// print_all_dos_in_collection.js
cursor = db.collection.find();
while ( cursor.hasNext() ) {
   printjson( cursor.next() );
}
# eval method
mongo test --eval \
  "printjson(db.collection.find().forEach(printjson))"
# execute for DB
mongo localhost:27017/test show_collection.js

READ

PROJECTIONS

AGGREGATE

MAP REDUCE

BACKUPS

# backup collection
mongodump --db test --collection collection

# backup db
mongodump --host mongodb1.example.net \
  --port 37017 \
  --username user \
  --password pass \
  --out /opt/backup/mongodump-2015-09-16
# import
mongoimport --db test \
  --collection collecton \
  --drop \
  --file dataset.json

TASK

  • Import dataset*

  • Find last 5 records
    with `Ave` in street
    and score >= 10

  • Show only street and 
    matched grades

Estimates: 20 min

INSERT DOCUMENT

db.inventory.insert(
   {
     item: "ABC1",
     details: {
        model: "14Q3",
        manufacturer: "XYZ Company"
     },
     stock: [ 
       { size: "S", qty: 25 }, 
       { size: "M", qty: 50 } 
     ],
     category: "clothing"
   }
)

INSERT DOCUMENTS

db.inventory.insert(
   [{
     item: "ABC1",
     details: {
        model: "14Q3",
        manufacturer: "XYZ Company"
     }
   },
   {
     item: "ABC2",
     details: {
        model: "14Q3",
        manufacturer: "XYZ Company"
     }
   }]
)

INSERT BULK

var bulk = db.inventory.initializeUnorderedBulkOp();

bulk.insert({
     item: "ABC1",
     details: {
        model: "14Q3",
        manufacturer: "XYZ Company"
     }
   }
);

bulk.insert({
     item: "ABC2",
     details: {
        model: "14Q3",
        manufacturer: "XYZ Company"
     }
   }
);

bulk.execute();

UPDATE DOCUMENT

db.inventory.update(
    { item: "MNO2" },
    {
      $set: {
        category: "apparel",
        details: { model: "14Q3", manufacturer: "XYZ Company" }
      },
      $currentDate: { lastModified: true }
    }
)

UPDATE DOCUMENTS

db.inventory.update(
   { category: "clothing" },
   {
     $set: { category: "apparel" },
     $currentDate: { lastModified: true }
   },
   { multi: true }
)

UPSERT DOCUMENT

db.inventory.update(
   { item: "TBD1" },
   {
     item: "TBD1",
     details: { "model" : "14Q4", "manufacturer" : "ABC Company" },
     stock: [ { "size" : "S", "qty" : 25 } ],
     category: "houseware"
   },
   { upsert: true }
)

REMOVE DOCUMENTS

// remove all
db.inventory.remove({})
// remove match condition
db.inventory.remove( { type : "food" } )
// remove match condition only one
db.inventory.remove( { type : "food" }, 1 )

MONGOOSE

Schema

Types

Models

Validation

CONNECT

$ npm install mongoose
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var db = mongoose.connection;

db.on('error', function(err) {
  console.error('connection error:', err));
});

db.once('open', function (callback) {
  console.log('connected');
});

SCHEMA

var animalSchema = new Schema({ name: String, type: String });

MODEL

var Animal = mongoose.model('Animal', animalSchema);

SCHEMA TYPES

  • String

  • Number

  • Date

  • Buffer

  • Boolean

  • Mixed

  • Objectid

  • Array

var schema = new Schema({
  name:    String,
  binary:  Buffer,
  living:  Boolean,
  updated: { type: Date, default: Date.now },
  age:     { type: Number, min: 18, max: 65 },
  mixed:   Schema.Types.Mixed,
  _someId: Schema.Types.ObjectId,
  array:      [],
  ofString:   [String],
  ofNumber:   [Number],
  ofDates:    [Date],
  ofBuffer:   [Buffer],
  ofBoolean:  [Boolean],
  ofMixed:    [Schema.Types.Mixed],
  ofObjectId: [Schema.Types.ObjectId],
  nested: {
    stuff: { type: String, lowercase: true, trim: true }
  }
})

INSTANCE METHODS

// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function (cb) {
  return this.model('Animal').find({ type: this.type }, cb);
}
var dog = new Animal({ type: 'dog' });

dog.findSimilarTypes(function (err, dogs) {
  console.log(dogs);
});

STATICS

// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function (name, cb) {
  return this.find({ name: new RegExp(name, 'i') }, cb);
}

var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function (err, animals) {
  console.log(animals);
});

VIRTUALS

// define a schema
var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

personSchema.virtual('name.full').get(function () {
  return this.name.first + ' ' + this.name.last;
});

// compile our model
var Person = mongoose.model('Person', personSchema);

// create a document
var bad = new Person({
    name: { first: 'Walter', last: 'White' }
});
console.log('%s is insane', bad.name.full); // Walter White is insane
personSchema.virtual('name.full').set(function (name) {
  var split = name.split(' ');
  this.name.first = split[0];
  this.name.last = split[1];
});

...

mad.name.full = 'Breaking Bad';
console.log(mad.name.first); // Breaking
console.log(mad.name.last);  // Bad
bad.name.full = 'Breaking Bad';

MODELS

var schema = new mongoose.Schema({ name: 'string', size: 'string' });
var Tank = mongoose.model('Tank', schema);
var small = new Tank({ size: 'small' });
small.save(function (err) {
  if (err) return handleError(err);
  // saved!
})

// or

Tank.create({ size: 'small' }, function (err, small) {
  if (err) return handleError(err);
  // saved!
})

ACTIONS

Tank.find({ size: 'small' })
    .where('createdDate')
    .gt(oneYearAgo)
    .exec(callback);
Tank.remove({ size: 'large' }, function (err) {
  if (err) return handleError(err);
  // removed!
});
Tank.update({name: 'Tiger'}, { 
  $set: {size: 'large'}
}, function (err) {
  if (err) return handleError(err);
  // updated!
});

VALIDATION

All - Required

Numbers - Min & Max

Strings - Enum & Match

Dates - Min & Max

CUSTOM

var toySchema = new Schema({
  color: String,
  name: String
});

var Toy = mongoose.model('Toy', toySchema);

Toy.schema.path('color').validate(function (value) {
  return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');

var toy = new Toy({ color: 'grease'});

toy.save(function (err) {  
  console.log(err.errors.color.message) 
  // prints 'Validator "Invalid color" failed 
  // for path color with value `grease`'
  
  console.log(err.name) // prints "ValidationError"
  console.log(err.message) // prints "Validation failed"
});

PASSPORT

Lighweight

Basic Auth

OAuth

OAuth2

LOCAL

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'Incorrect username.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: 'Incorrect password.' });
      }
      return done(null, user);
    });
  }
));
app.use(passport.initialize());

passport.serializeUser(function (user, done) {
    // console.log('user', user);
    done(null, user.username);
});

passport.deserializeUser(function (username, done) {
    // console.log('username', username);
    User.findOne({
        username: username
    }, function (err, user) {
        return done(err, user);
    });
})

app.post('/login', passport.authenticate('local', { 
    successRedirect: '/',
    failureRedirect: '/login' }
));

OPEN ID

var passport = require('passport')
  , OpenIDStrategy = require('passport-openid').Strategy;

passport.use(new OpenIDStrategy({
    returnURL: 'http://www.example.com/auth/openid/return',
    realm: 'http://www.example.com/'
  },
  function(identifier, done) {
    User.findOrCreate({ openId: identifier }, function(err, user) {
      done(err, user);
    });
  }
));
// Accept the OpenID identifier and redirect the user to their OpenID
// provider for authentication.  When complete, the provider will redirect
// the user back to the application at:
//     /auth/openid/return
app.post('/auth/openid', passport.authenticate('openid'));

// The OpenID provider has redirected the user back to the application.
// Finish the authentication process by verifying the assertion.  If valid,
// the user will be logged in.  Otherwise, authentication has failed.
app.get('/auth/openid/return',
  passport.authenticate('openid', { successRedirect: '/',
                                    failureRedirect: '/login' }));

OAUTH 1.0

var passport = require('passport')
  , OAuthStrategy = require('passport-oauth').OAuthStrategy;

passport.use('provider', new OAuthStrategy({
    requestTokenURL: 'https://www.provider.com/oauth/request_token',
    accessTokenURL: 'https://www.provider.com/oauth/access_token',
    userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
    consumerKey: '123-456-789',
    consumerSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(token, tokenSecret, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

OAUTH 2.0

var passport = require('passport')
  , OAuth2Strategy = require('passport-oauth').OAuth2Strategy;

passport.use('provider', new OAuth2Strategy({
    authorizationURL: 'https://www.provider.com/oauth2/authorize',
    tokenURL: 'https://www.provider.com/oauth2/token',
    clientID: '123-456-789',
    clientSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(accessToken, refreshToken, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));
// only one scope
app.get('/auth/provider',
  passport.authenticate('provider', { scope: 'email' })
);


// multiple scopes
app.get('/auth/provider',
  passport.authenticate('provider', { scope: ['email', 'sms'] })
);

Q&A Time

ANDRIY DACENKO

SOFTWARE ENGINEER

September 16, 2015

NODE.JS EXTENDED FEATURES

 SOCKETS, MONGO, PASSPORT

Node.js extended features

By Andrew Dacenko

Node.js extended features

JS Mentoring program Kyiv.2015-06 Module 5: Server programming using Node.js Lecture15: Node.js extended features

  • 1,245