Search This Blog

Tuesday, February 27, 2018

Express.js - Passport authentication using postgreSQL

Lets try to implement 'passport-local' authentication using postgreSQL datastore.

first create table
    create table pass_auth_user
    (
        id  SERIAL primary key,
        username varchar(50),
        pass varchar(50),
        createdon timestamp without time zone   
    )

insert a user against which we can check login

    insert into pass_auth_user(username,pass,createdon) values('sangram','abdc1234',now())

lets write a postgres function that will query database for valid user & password combination.

    CREATE OR REPLACE FUNCTION public.fn_autheticate_local(    p_username varchar(50),p_pass varchar(50))
    RETURNS TABLE(id integer, username varchar(50),pass varchar(50),createdon timestamp without time zone)
    LANGUAGE 'plpgsql'
    AS $BODY$

    begin
        RETURN QUERY
        select       
        a.id,
        a.username,
        a.pass,
        a.createdon
        from
        pass_auth_user a
        where
            a.username = p_username
        and a.pass = p_pass
        limit 1;
    end

    $BODY$;


lets check our postgres function by running following query

    select * from fn_autheticate_local('sangram','abdc1234')


we need to install pg,connect-ensure-login,passport & passport-local library using npm.

lets create a pgConnection.js add generic function that will run query & provide result.

model/pgConnection.js

    var pg = require('pg');

    const dbConfig = {
        user: 'xdba',
        database: 'xplay',
        password: 'sangram',
        port: 5432
    };
    const pool = new pg.Pool(dbConfig);



    module.exports = {
        ExecuteQuery: function (query, callback) {
        pool.connect(function (err, client, done) {
            if (err) {
                callback(err, null);
            }
            client.query(query, function (err, result) {
                done();
                if (err) {
                    callback(err, null);
                }
                callback(null, result.rows);
            })
        })
        }
    }

I am writing a userService.js that will keep all database queries through function in one place for our table 'pass_auth_user'

service/userService.js

    var pgConnection = require('../model/pgConnection');

    module.exports = {

        pg_auth: function (username, password, callback) {


        var _selectQuery = "select * from fn_autheticate_local('" + username + "','" + password + "')";
        console.log('query:' + _selectQuery);

        try {
            pgConnection.ExecuteQuery(_selectQuery, function (err, result) {
                if (err) {
                    callback(err, null);
                }
                else {
                    callback(null, result);
                }
            });
        } catch (exp) {
            callback(exp, null);
        }
        },

        getById: function (userid, callback) {


        var _selectQuery = "select * from pass_auth_user where Id =" + userid;
        console.log('query:' + _selectQuery);

        try {
            pgConnection.ExecuteQuery(_selectQuery, function (err, result) {
                if (err) {
                    callback(err, null);
                }
                else {
                    callback(null, result);
                }
            });
        } catch (exp) {
            callback(exp, null);
        }
        }
    }

service/index.js
    var services = {
        userService: require('../service/userService.js')
     };
   
     module.exports = services;

route/index.js
    var express = require('express');
    var router = express.Router();
    var passport = require('passport')
    var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;

    /* GET home page. */
    router.get('/', function (req, res, next) {
      res.render('index', { title: 'Express' });
    });

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

    router.get('/authhome',ensureLoggedIn('/login'), function (req, res, next) {
      console.log(req.user);
      res.render('authhome');
    });

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

    router.get('/logout', function (req, res) {
      req.logout();
      res.redirect('/');
    });

    module.exports = router;


app.js

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    var services = require('./service');


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

    var index = require('./routes/index');
    var users = require('./routes/users');

    var app = express();

    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');



    //passport
    app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
    app.use(passport.initialize());
    app.use(passport.session());


    passport.use(new LocalStrategy(
      function (username, password, cb) {
        services.userService.pg_auth(username, password, function (err, result) {

          if (err) {
        return cb(err);
          }
          var user = result[0];
          console.log('Result' + JSON.stringify(user));
          if (!user) {
        return cb(null, false);
          }

          if (user.pass != password) {
        return cb(null, false);
          }
          return cb(null, user);
        })
      }));


    passport.serializeUser(function (user, cb) {
      cb(null, user.id);
    });

    passport.deserializeUser(function (id, cb) {
      services.userService.getById(id, function (err, result) {
        if (err) {
          return cb(err);
        }

        var user = result[0];
        cb(null, user);
      });
    });


    // uncomment after placing your favicon in /public
    //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));

    app.use('/', index);
    app.use('/users', users);

    // catch 404 and forward to error handler
    app.use(function (req, res, next) {
      var err = new Error('Not Found');
      err.status = 404;
      next(err);
    });

    // error handler
    app.use(function (err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};

      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });

    module.exports = app;


Here we need to write two functions one for serialization & other for deserialization of user object.Writing two method one that takes username/password and return user record from postgre while second take userid and return corresponding user record.

After login user get redirected to root route i.e. '/'.to guard certain route from un authenticated users we are using 'connect-ensure-login' package that  provide 'ensureLoggedIn' method which is used as middleware to guard home page of autheticated user.

passport saves serialized user in req.user which can be viewed by printing same over console.

Sunday, February 25, 2018

Abstract Factory Pattern in node.js

Abstract Factory provide an interface for creating families of related or dependent objects without specifying their concrete classes.

In the example we have two factories for creating shoes ,one for creating 'loafer' other for creating 'wingtip'.

consider ShoeFactory.js

    function Loafer(name) {
        this.name = name;

        this.say = function () {
        log.add("I am Loafer from " + name);
        };
    }

    function LoaferFactory() {

        this.create = function (name) {
        return new Loafer(name);
        };
    }

    function Wingtip(name) {
        this.name = name;

        this.say = function () {
        log.add("I am Wingtip from " + name);
        };
    }

    function WingtipFactory() {

        this.create = function (name) {
        return new Wingtip(name);
        };
    }

    var log = (function () {
        var log = "";

        return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { console.log(log); log = ""; }
        }
    })();


    module.exports = {
        LoaferFactory: LoaferFactory,
        WingtipFactory: WingtipFactory,
        log:log
    }

Here is index.js

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

    var shoes = [];
    var loaferFactory = new ShoeFactory.LoaferFactory();
    var wingtipFactory = new ShoeFactory.WingtipFactory();

    shoes.push(loaferFactory.create("Lotto"));
    shoes.push(loaferFactory.create("Showman"));

    shoes.push(wingtipFactory.create("Bacca Bucci"));
    shoes.push(wingtipFactory.create("Jorden"));

    for (var i = 0, len = shoes.length; i < len; i++) {
        shoes[i].say();
    }

    ShoeFactory.log.show();

Here classes 'Loafer' & 'Wingtip' had same properties and methods like they are inheriting from same interface.

For checking our abstract factory lets run entry point

node index.js

output:
    I am Loafer from Lotto
    I am Loafer from Showman
    I am Wingtip from Bacca Bucci
    I am Wingtip from Jorden

Here we are creating object of related classes using factory method.

references:
    http://www.dofactory.com/javascript/abstract-factory-design-pattern

Observer Pattern in Node.js

The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state.

The Observer pattern requires that the observer (or object) wishing to receive topic notifications must subscribe this interest to the object firing the event (the subject).

One or more observers are interested in the state of a subject and register their interest with the subject by attaching themselves. When something changes in our subject that the observer may be interested in, a notify message is sent which calls the update method in each observer. When the observer is no longer interested in the subject's state, they can simply detach themselves

We will try to implement Observer pattern.

Observable.js

    var EventEmitter = require('events').EventEmitter;

    class Observable extends EventEmitter {
        constructor() {
        super()
        }

        RaiseEvent(event_name, event_args) {
        console.log("Raising Event:" + event_name);
        this.emit(event_name, event_args);
        }

        Subscribe(event_name, listener) {
        console.log("Adding Listener for Event :" + event_name);
        this.on(event_name, listener);
        }

        Unsubscribe(event_name, listener) {
        console.log("Removing Listener for Event:" + event_name);
        this.removeListener(event_name, listener);
        }

        ListenerCount(event_name)
        {
        this.listenerCount(this, event_name)
        }
    }

    module.exports = Observable;


Testing the 'Observable' class.

index.js

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

    var observable = new Observable();

    var greet1 = function (name) {
        console.log('Hello ,' + name);
    }

    var greet2 = function (name) {
        console.log('How are you,' + name);
    }

    var bye1 = function (name) {
      console.log('Bye Bye ,' + name);
    }


    //two listeners get attached to 'greet' event
    observable.Subscribe('greet', greet1);
    observable.Subscribe('greet', greet2);

    observable.Subscribe('bye', bye1);

    console.log("Total listeners for 'greet' event = " + observable.listenerCount('greet') );
    console.log("Total listeners for 'bye' event = " + observable.listenerCount('bye') );

    //raising event for greet
    observable.RaiseEvent('greet', 'sangram')

    //raising event for swara
    observable.RaiseEvent('greet', 'swara')

    //raising event for sangram
    observable.RaiseEvent('bye', 'sangram')


    //remove greet1 listener
    observable.Unsubscribe('greet', greet1)
    console.log("Total listeners for 'greet' event = " + observable.listenerCount('greet'));


    //raising event greet for sangram
    observable.RaiseEvent('greet', 'sangram')

Run node index.js


output:
    Adding Listener for Event :greet
    Adding Listener for Event :greet
    Adding Listener for Event :bye
    Total listeners for 'greet' event = 2
    Total listeners for 'bye' event = 1
    Raising Event:greet
    Hello ,sangram
    How are you,sangram
    Raising Event:greet
    Hello ,swara
    How are you,swara
    Raising Event:bye
    Bye Bye ,sangram
    Removing Listener for Event:greet
    Total listeners for 'greet' event = 1
    Raising Event:greet
    How are you,sangram   

Express.js Uploading files using multer

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.

To demonstrate upload using multer we will first create a view say 'upload.ejs' which 'multipart/form-data' form.


<!DOCTYPE html>
<html>

<head>
  <title>
    <%= title %>
  </title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
  <h1>
    <%= title %>
  </h1>
  <p>Welcome to
    <%= title %>
  </p>

  <form method="post" enctype="multipart/form-data" action="/upload" >
    <table>
      <tr>
        <td colspan="2">
          <%if(uploaded.length > 0){%>
            <p><b>Uploaded Files:</b></p>
            <%for( var i=0;i < uploaded.length;i++){%>
              <p><%=uploaded[i]%></p>
            <%}
          }%>

          <%if(message !=''){%>
            <p><b><%=message%></b></p>
          <%}%>
        </td>
      </tr>
      <tr>
        <td>Profile Pic (*.jpg|*.jpeg)</td>
        <td>
          <input type="file" name="profile_pic" />
        </td>
      </tr>
      <tr>
        <td>Text Data (*.txt) </td>
        <td>
          <input type="file" name="text_data" />
        </td>
      </tr>
      <tr>
        <td></td>
        <td>
          <input type="submit" name="Upload" />
        </td>
      </tr>
    </table>
  </form>
</body>
</html>
</pre>

Now lets take a look at app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var uuid = require('uuid/v4')

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

//multer
const multer = require('multer');




var commonModules = {
  "express": express,
  "app": app,
  "multer": multer,
  'uuid':uuid
};

var route = require('./routes/index')(commonModules);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

Here we are creating a commonModule collection and passing it to route

My route file index.js is

module.exports = function (commonModules) {
  var defaultRoute = commonModules.express.Router();
  var uuid = commonModules.uuid;
  var multer = commonModules.multer;


  var storageOptions = multer.diskStorage({
    destination: function (req, file, cb) {
      if (file.fieldname == 'profile_pic' && file.mimetype == 'image/jpeg') {
        cb(null, 'uploads/profile_pic/')
      }

      if (file.fieldname == 'profile_pic' && file.mimetype != 'image/jpeg') {
        console.log('please upload valid jpeg file');
        cb({ message: 'please upload valid jpeg file' }, null);
      }

      if (file.fieldname == 'text_data' && file.mimetype == 'text/plain') {
        cb(null, 'uploads/text_data/')
      }

      if (file.fieldname == 'text_data' && file.mimetype != 'text/plain') {
        console.log('please upload valid text file');
        cb({ message: 'please upload valid text file' }, null);
      }
    },
    filename: function (req, file, cb) {
      var fileparts = file.originalname.split('.');
      var ext = fileparts[fileparts.length - 1];
      cb(null, file.fieldname + '-' + Date.now() + '.' + ext)
    }
  });

  /* GET home page. */
  defaultRoute.get('/', function (req, res, next) {
    var viewData = {
      title: 'Express',
      uploaded: []
    }
    res.render('index', viewData);
  });


  //GET
  defaultRoute.get('/upload', function (req, res, next) {
    console.log('GET:upload');

    var uploaded = [];

    var viewData = {
      title: 'Upload Demo',
      uploaded: uploaded,
      message: ''
    }
    res.render('upload', viewData);
  });

  //post
  var file_fields = [{ name: 'profile_pic', maxCount: 1 }, { name: 'text_data', maxCount: 1 }];
  var upload_fields = multer({
    storage: storageOptions,
    limits: {
      fileSize: 1000000
    }
  }).fields(file_fields);
  defaultRoute.post('/upload/field', function (req, res, next) {
    upload_fields(req, res, function (err) {
      var uploaded = [];
      var message = '';
      var viewData = {};

      if (err) {
        message = err.message;
      } else {
        uploaded.push(req.files.profile_pic[0].filename);
        uploaded.push(req.files.text_data[0].filename);
      }

      viewData = {
        title: 'Upload Demo',
        uploaded: uploaded,
        message: message
      }

      console.log(viewData);

      res.render('upload', viewData);
    });
  });

  //upload demo
  var uploadAny = multer({
    storage: storageOptions,
    limits: {
      fileSize: 1000000
    }
  }).any();
  defaultRoute.post('/upload', function (req, res, next) {
    uploadAny(req, res, function (err) {
      var uploaded = [];
      var errors = [];
      var message = '';
      var viewData = {};

      if (err) {
        message = err.message;
      } else {
        for (var i = 0; i < req.files.length; i++) {
          uploaded.push(req.files[i].filename);
        }
      }

      viewData = {
        title: 'Upload Demo',
        uploaded: uploaded,
        message: message
      }
      res.render('upload', viewData);
    })
  });

  commonModules.app.use('/', defaultRoute);
}

Our upload.ejs contain a form with two file fields 'text_data' and 'profile_pic' and form has action '/upload'.In route we are validating that 'profile_pic' contain jpeg & 'text_data' contain plain text file else it raises error.If either of file field has invalid upload then upload of both file field fails.

Created 'uploads' folder in root directory which contain two folders 'profile_pic' & 'text_data'. which contain uploaded image & uploaded text file respectively.

multer upload has been implemented in two different post routes '/upload/field' & '/upload'
The route '/upload/field' uploads using multers 'any' function while route '/upload' implements multer's 'field' functionlity.

For testing purpose visit http://localhost:3000/upload & select text file in first input type file & submit the error message will be displayed as 'please upload valid jpeg file'.
Now in second file field & first file field select and image & submit you will get error message 'please upload valid jpeg file'.

If we select image in first file field & text in second file field then files get uploaded in respective directories '/uploads/profile_pic' & '/uploads/text_data'.
 
Code for the application is available at https://github.com/gitsangramdesai/express-multer-upload.
 

Friday, February 23, 2018

Using Redis as Session Store In Express.js

http protocol  being stateless,to maintain state across multiple request from client  to server usually cookies & sessions as used along to URL params.
Cookies get stored in client browser,it has been assigned expiry time.Untill cookies from given server is alive it get passed to server in every subsequent request

As cookies are on client we need to obsure it using some sort of hashing algorithm.otherwise person with malicious intent will tamper it.

   'Express-session' is more abstract compared to cookie-based, it supports different session stores like files, DB, cache on contratory 'cookie-session' is a simple & lightweight ,
it support only cookie storage engine.Meaning in cookie-session all the session info is stored on the client in a cookie.
     'Express-session' stores just a mere session identifier within a cookie in the client end, whilst storing the session data entirely on the session store.

UUIDs are for practical purposes unique, without depending for their uniqueness on a central registration authority or coordination between the parties generating them, unlike most other numbering schemes. While the probability that a UUID will be duplicated is not zero, it is close enough to zero to be negligible.We are using uuid for creating unique session id.

Redis is in memory key-value pair database,retrieval data in case of redis is faster compared to RDBMS as its in memory compared to in Disk.

Lets see how we can use redis to store session.First we need install required packages express-session,connect-redis,redis,uuid.

In our existing express.js project created using express generator  inside app.js add

    var session = require('express-session');
    var redisStore = require('connect-redis')(session);

    var redis   = require("redis");
   //var client  = redis.createClient();

    const redisPassword = "sangram";
    var client = redis.createClient({
     host: '127.0.0.1',
     no_ready_check: true,
    auth_pass: redisPassword,
   });
   



    var uuid = require('uuid/v4')


now add middleware which will attach redisstore as session store in express-session

app.use(session({
  genid: function(req) {
    var uid = uuid() 
    console.log('UId :' + uid);
    return uid;
  },
  secret: 'my_secret',
  // create new redis store.
  store: new redisStore({ host: 'localhost', port: 6379, client: client,ttl :  260}),
  saveUninitialized: false,
  resave: false
}));


Here genid function is giving unique ids to session for new session of client.

Create a Login Page login.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <form method="POST" action="/login">
      <table style="border:1px solid red;background-color:skyblue;color:white;">
        <tr>
          <td colspan="2">
            Login to express demo
          </td>
        </tr>
        <tr>
          <td>Username</td>
          <td>
            <input type="text" name="username" id="username" required />
          </td>
        </tr>
        <tr>
          <td>password</td>
          <td>
            <input type="password" name="password" id="password" required />
          </td>
        </tr>
        <tr>
          <td></td>
          <td>
              <input type="submit" value="Login" style="background-color:orange" />
          </td>
        </tr>
      </table>
    </form>
  </body>
</html>

also create a dummy view for authentication home

authhome.ejs

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <%= username %>
  </body>
</html>

then create route for login in routes/index.js

router.get('/login', function (req, res, next) {
  res.render('login', { title: 'login' });
});


router.get('/bar', function (req, res, next) {
  req.session.city = 'Mumbai';
  console.log("Bar:city:" + req.session.city);
  res.redirect('/authhome')
});

router.get('/authhome', function (req, res, next) {
  console.log("AuthHome:city:" + req.session.city);
  res.render('authhome', { title: 'auth-home', username: req.session.username,city: req.session.city });
});

router.post('/login', function (req, res, next) {
  req.session.username = req.body.username;
  req.session.city = 'Mumbai';
  res.redirect('/authhome')
});

router.post('/logout', function (req, res, next) {
  req.session.destroy(function (err) {
    if (err) {
      console.log(err);
    } else {
      res.redirect('/home')
    }
  });

});


Now visit http://localhost:3000/ & using your browser developer tool confirm no cookies set by http://localhost:3000/,
To create session & hence cookie Visit http://localhost:3000/login still no cookie set.

Now provide some dummy username & password & submit,it will not validate credential as it is not put in place put will add 'username' to session.
After setting this value user is redirected to http://localhost:3000/authhome which print username saved in session.While City will be empty.

Now hit http://localhost:3000/bar in new tab ,this GET request set 'city' key in session. After setting this value user is redirected to http://localhost:3000/authhome
Here both username & city are displayed on View.

Refresh previous tab it will also show username & city in view.

If you repeat same using other browser you will see different session ids are generated & 'username' value stored in session is not overlapping with each other.

To see keys generated in redis launch redis-cli on terminal.'KEYS *' will list all keys .

Using one of key printed in 'Keys *' output say 'sess:5d5ee6f0-c835-4672-a3f2-f201f51e46b7' you can see cookies set to client browser run below command in redis-cli

    GET sess:5d5ee6f0-c835-4672-a3f2-f201f51e46b7
        output:
        "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"},\"username\":\"sangram\",\"city\":\"Mumbai\"}"


'flushdb' is used to remove all keys in current database.

You can cross verify keys with those printed on console from session middleware function 'genid' for generating unique session id.

Code of this article can be viewed at https://github.com/gitsangramdesai/redis-session-store-express

Thursday, February 15, 2018

Install MonoDevelop using Flatpak

To install FlatPak onto your debian machine add respository & then install flatpak package.

    Add Repositoy for flatpak:
        wget -O - https://sdk.gnome.org/apt/debian/conf/alexl.gpg.key|apt-key add -
        echo "deb [arch=amd64] https://sdk.gnome.org/apt/debian/ jessie main" > /etc/apt/sources.list.d/flatpak.list
 

    Update Package List
           we can install flatpak by first updating package list         
                       apt update   
                       apt install flatpak
 

    Testing flatpak with monodevlop:
       You can download monodevelop.flatpakref from monodevlop.org portal naviagate to your download directory

             flatpak install --user --from monodevelop.flatpakref
                         or
             flatpak install --user --from https://mono-project.azureedge.net/repo/monodevelop.flatpakref
 

    To launch mono-develop with flatpack go ahead and run below line
        flatpak run com.xamarin.MonoDevelop

Running Express.js in Cluster

Even if the computer has more than one CPU core, Node.js does not use all
of them by default. It only uses one CPU core for the main thread that
handles the event loop. So, for example, if you have a four-core system,
Node will only use one of them by default.

cluster module lets us create child processes that runs simultaneously
on common server port.Module enable the cluster module anyway
to increase your server availability and fault-tolerance

Including cluster in our express.js app

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const express = require("express");
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);

// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}

cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
const app = express();
app.listen('3005', function () {
console.log('Listening on port:' + '3005');
});

console.log(`Worker ${process.pid} started`);
}

app.get("/", (req, res) => {
res.send("Hello World!");
});
app.get("/api/:n", function (req, res) {
let n = parseInt(req.params.n);
let count = 0;
if (n > 5000000000) n = 5000000000;
for (let i = 0; i <= n; i++) {
count += i;
}
res.send(`Final count is ${count} for ${n}`);
});

Run your express entry point as
node server.js

Output:
Master 23225 is running
Worker 23236 started
Worker 23249 started
Listening on port:3005
Listening on port:3005
Worker 23242 started
Listening on port:3005
Worker 23241 started
Listening on port:3005

Checking same through ps command on bash prompt

Run below command
ps -aux | grep server.js
Output:
sangram 23225 0.8 0.5 1023304 46656 pts/1 Sl+ 09:43 0:00 node server.js
sangram 23236 1.2 0.5 1022656 47044 pts/1 Sl+ 09:43 0:00 /usr/local/bin/node /home/sangram/xpw/app/server.js
sangram 23241 1.1 0.5 1022660 46728 pts/1 Sl+ 09:43 0:00 /usr/local/bin/node /home/sangram/xpw/app/server.js
sangram 23242 1.1 0.5 1022728 47292 pts/1 Sl+ 09:43 0:00 /usr/local/bin/node /home/sangram/xpw/app/server.js
sangram 23249 1.2 0.5 1022664 46992 pts/1 Sl+ 09:43 0:00 /usr/local/bin/node /home/sangram/xpw/app/server.js
Cluster on non-window platform follows round-robin approach,
where the master process listens on a port,
accepts new connections and distributes them across the workers
in a round-robin fashion, with some built-in smarts to avoid overloading
a worker process.

As workers are all separate processes, they can be killed
or re-spawned depending on a program's needs,
without affecting other workers. As long as there are some workers
still alive, the server will continue to accept connections.
If no workers are alive, existing connections will be dropped
and new connections will be refused.
It is the application's responsibility
to manage the worker pool based on its own needs.

Install Apache Benchmark Tool

sudo apt-get install apache2-utils

Now lets test

ab -c1000 -t10 http://localhost:3005/

This command will test-load the server with 1000
concurrent connections for 10 seconds.

Sample Output:

Server Software:
Server Hostname: localhost
Server Port: 3005

Document Path: /
Document Length: 139 bytes

Concurrency Level: 1000
Time taken for tests: 7.660 seconds
Complete requests: 50000
Failed requests: 0
Non-2xx responses: 50000
Total transferred: 20750000 bytes
HTML transferred: 6950000 bytes
Requests per second: 6527.02 [#/sec] (mean)
Time per request: 153.209 [ms] (mean)
Time per request: 0.153 [ms] (mean, across all concurrent requests)
Transfer rate: 2645.23 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 83 300.6 0 3048
Processing: 10 35 253.1 15 6634
Waiting: 10 35 253.1 15 6634
Total: 11 118 432.1 15 7648

Percentage of the requests served within a certain time (ms)
50% 15
66% 17
75% 18
80% 20
90% 34
95% 1036
98% 1241
99% 1452
100% 7648 (longest request)

using this tool you can find 'Requests per second','Time per request' ,
'no of failed request' etc.

References:
https://nodejs.org/api/cluster.html




Wednesday, February 14, 2018

Mongo Query and SQL query Side by Side - Part 2

Continuing further from earlier post "Mongo Query and SQL query Side by Side", we will further explore some more mongo clauses.

 How to escape hypenated collection name in mongo shell:
      Our collection 'order-details' contains hypen in between when we try below query it does not work
       
        db.order-details.find().limit(1).pretty()

    so we need to use other syntax for quering this collection below both works well

        db['order-details'].find().pretty()   
                        or
        db.getCollection('order-details').find({}).pretty() 

Counting records

    Select count(*) from ordercopy where ShipPostalCode=98124

    db.ordercopy.find({"ShipPostalCode":98124}).count()
    db.ordercopy.count({"ShipPostalCode":98124})


Distinct
    select distinct ShipPostalCode from ordercopy where ShipCountry="USA"

    db.ordercopy.distinct( "ShipPostalCode" ,{"ShipCountry":"USA"})
   
    Output:
            [
                87110,
                98124,
                82520,
                99508,
                97219,
                97201,
                83720,
                97827,
                99362,
                97403,
                98034,
                94117,
                59801
            ]
   
    Query Returned distinct ShipPostalcode values as an array.

Distinct on Multiple Columns:

SQL:select distinct ShipName,ShipCity from Orders

db.orders.aggregate(
[
    {
        "$group":{
            "_id":{ShipName:"$ShipName",ShipCity:"$ShipCity"}
            }
    }
])
Output:
{ "_id" : { "ShipName" : "France restauration", "ShipCity" : "rue Royale" } }
{ "_id" : { "ShipName" : "Wilman Kala", "ShipCity" : "Helsinki" } }
{ "_id" : { "ShipName" : "Let's Stop N Shop", "ShipCity" : "San Francisco" } }
{ "_id" : { "ShipName" : "Trail's Head Gourmet Provisioners", "ShipCity" : "Kirkland" } }
{ "_id" : { "ShipName" : "Great Lakes Food Market", "ShipCity" : "Eugene" } }
{ "_id" : { "ShipName" : "Alfreds Futterkiste", "ShipCity" : "Berlin" } }
{ "_id" : { "ShipName" : "Océano Atlántico Ltda.", "ShipCity" : "Buenos Aires" } }
{ "_id" : { "ShipName" : "Alfred's Futterkiste", "ShipCity" : "Berlin" } }
{ "_id" : { "ShipName" : "LINO-Delicateses", "ShipCity" : "I. de Margarita" } }
{ "_id" : { "ShipName" : "La corne d'abondance", "ShipCity" : "avenue de l'Europe" } }
{ "_id" : { "ShipName" : "Consolidated Holdings", "ShipCity" : "London" } }
{ "_id" : { "ShipName" : "Bottom-Dollar Markets", "ShipCity" : "Tsawassen" } }
{ "_id" : { "ShipName" : "Wolski Zajazd", "ShipCity" : "Warszawa" } }
{ "_id" : { "ShipName" : "Folies gourmandes", "ShipCity" : "chaussée de Tournai" } }
{ "_id" : { "ShipName" : "Hungry Coyote Import Store", "ShipCity" : "Elgin" } }
{ "_id" : { "ShipName" : "Queen Cozinha", "ShipCity" : 891 } }
{ "_id" : { "ShipName" : "Vaffeljernet", "ShipCity" : "Ã…rhus" } }
{ "_id" : { "ShipName" : "Laughing Bacchus Wine Cellars", "ShipCity" : "Vancouver" } }
{ "_id" : { "ShipName" : "Around the Horn", "ShipCity" : "Colchester" } }
{ "_id" : { "ShipName" : "Piccolo und mehr", "ShipCity" : "Salzburg" } }


Group by Count:

        a]  select ShipPostalCode,count(*) from ordercopy group by ShipPostalCode
            db.ordercopy.aggregate([
                {"$group" : {_id:"$ShipPostalCode", count:{$sum:1}}}
            ])   

        b]
            select ShipPostalCode,ShipCountry,count(*) from ordercopy group by ShipPostalCode,ShipCountry

            db.ordercopy.aggregate(
                {
                    "$group" : 
                        {
                            _id : {ShipPostalCode:"$ShipPostalCode", ShipCountry: "$ShipCountry"},
                            count : { $sum : 1}
                        }
                }
            )
        c]
        select ShipPostalCode,ShipCountry,count(*) from ordercopy group by ShipPostalCode,ShipCountry order by count(*)

        db.ordercopy.aggregate(
                {
                    "$group" : 
                        {
                            _id : {ShipPostalCode:"$ShipPostalCode", ShipCountry: "$ShipCountry"},
                            count : { $sum : 1}
                        }
                },
                {
                    $sort:{"count":-1}
                }
            )

        d]
        select ShipPostalCode,ShipCountry,count(*) from ordercopy group by ShipPostalCode,ShipCountry order by ShipCountry desc,ShipPostalCode desc
        db.ordercopy.aggregate(
                {
                    "$group" : 
                        {
                            _id : {ShipPostalCode:"$ShipPostalCode", ShipCountry: "$ShipCountry"},
                            count : { $sum : 1}
                        }
                },
                {
                    $sort:{"_id.ShipCountry":-1,"_id.ShipPostalCode":-1}
                }
            )
f) group by sum of column or computed column

    db.getCollection('order-details').aggregate(
        {
            "$group" : 
                {
                    _id : {OrderID:"$OrderID"},
                    totalAmount: { $sum: { $multiply: [ "$UnitPrice", "$Quantity" ] } },
                }
        },
        {
            $sort:{"_id.OrderID":1}
        }
    )
g) in clause

    select * from ordercopy where ShipPostalCode in (8010,98124)

    db.ordercopy.find({
        ShipPostalCode :{ $in : [8010,98124]}
    })

h) Not equal to
    select * from ordercopy where ShipCountry != 'Austria'

    db.ordercopy.find({"ShipCountry": {$ne: "Austria"}})

i) Not In
    select * from ordercopy where ShipCountry not in ('Austria','Finland')

    db.ordercopy.find({
        ShipCountry :{ $nin : ['Austria','Finland']}
    })

j) $all clause

download https://github.com/ozlerhakan/mongodb-json-files/blob/master/datasets/palbum.zip
extract zip & import it to palbum database

mongoimport --db palbum --collection albums --drop --file  /path_to_palbum_dir/albums.json
mongoimport --db palbum --collection images --drop --file /path_to_palbum_dir/images.json


a) below query select all records where tags array contain both 'sunrises' & 'travel' and may be other tag extra with this two
db.images.find( { tags: { $all: [ "sunrises", "travel"] } } )

Output:
    { "_id" : 1, "height" : 480, "width" : 640, "tags" : [ "cats", "sunrises", "kittens", "travel", "vacation", "work" ] }
    { "_id" : 4, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "kittens", "travel" ] }
    { "_id" : 8, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "sunrises", "kittens", "travel" ] }
    { "_id" : 9, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "travel" ] }
    { "_id" : 21, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "sunrises", "travel", "vacation" ] }
    { "_id" : 19, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "kittens", "travel", "work" ] }
    { "_id" : 22, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel", "vacation", "work" ] }
    { "_id" : 24, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "sunrises", "kittens", "travel", "vacation" ] }
    { "_id" : 26, "height" : 480, "width" : 640, "tags" : [ "sunrises", "kittens", "travel", "vacation" ] }
    { "_id" : 28, "height" : 480, "width" : 640, "tags" : [ "sunrises", "kittens", "travel" ] }
    { "_id" : 32, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "sunrises", "kittens", "travel", "vacation", "work" ] }
    { "_id" : 27, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "kittens", "travel", "work" ] }
    { "_id" : 43, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "sunrises", "kittens", "travel", "vacation" ] }
    { "_id" : 45, "height" : 480, "width" : 640, "tags" : [ "cats", "sunrises", "travel", "vacation", "work" ] }
    { "_id" : 51, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel", "vacation", "work" ] }
    { "_id" : 52, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "sunrises", "kittens", "travel", "work" ] }
    { "_id" : 57, "height" : 480, "width" : 640, "tags" : [ "cats", "sunrises", "travel", "work" ] }
    { "_id" : 58, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel", "vacation", "work" ] }
    { "_id" : 59, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "kittens", "travel" ] }
    { "_id" : 71, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }


b) below query select all records where tags array contain both 'sunrises' & 'travel' but nothing extra tag with it
db.images.find( { tags: { $all: [ [ "sunrises", "travel" ] ] } } )

Output:
    { "_id" : 71, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 89, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 463, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 548, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 557, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 1065, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 1095, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 1145, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 1584, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 1619, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 1701, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2052, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2249, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2256, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2275, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2664, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2817, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 2969, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 3220, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }
    { "_id" : 3320, "height" : 480, "width" : 640, "tags" : [ "sunrises", "travel" ] }


How to Drop database in mongo
 suppose you want to drop database named 'grades'  then to drop it
     first switch to 'grades' & then issue 'dropDatabase'

     use grades
     db.dropDatabase()


$size clause in mongodb
    Below query will select all records where 'tags' array has length 3'

    db.images.find( { tags: { $size: 3 } } )

    Output:

        { "_id" : 2, "height" : 480, "width" : 640, "tags" : [ "dogs", "kittens", "work" ] }
        { "_id" : 12, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "travel" ] }
        { "_id" : 14, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "vacation" ] }
        { "_id" : 17, "height" : 480, "width" : 640, "tags" : [ "kittens", "vacation", "work" ] }
        { "_id" : 9, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "travel" ] }
        { "_id" : 23, "height" : 480, "width" : 640, "tags" : [ "cats", "kittens", "travel" ] }
        { "_id" : 29, "height" : 480, "width" : 640, "tags" : [ "kittens", "vacation", "work" ] }
        { "_id" : 30, "height" : 480, "width" : 640, "tags" : [ "cats", "sunrises", "vacation" ] }
        { "_id" : 28, "height" : 480, "width" : 640, "tags" : [ "sunrises", "kittens", "travel" ] }
        { "_id" : 33, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "kittens" ] }
        { "_id" : 36, "height" : 480, "width" : 640, "tags" : [ "dogs", "kittens", "vacation" ] }
        { "_id" : 38, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "work" ] }
        { "_id" : 47, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "travel" ] }
        { "_id" : 50, "height" : 480, "width" : 640, "tags" : [ "sunrises", "vacation", "work" ] }
        { "_id" : 49, "height" : 480, "width" : 640, "tags" : [ "dogs", "cats", "travel" ] }
        { "_id" : 53, "height" : 480, "width" : 640, "tags" : [ "sunrises", "vacation", "work" ] }
        { "_id" : 56, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "vacation" ] }
        { "_id" : 48, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "work" ] }
        { "_id" : 61, "height" : 480, "width" : 640, "tags" : [ "kittens", "travel", "vacation" ] }
        { "_id" : 60, "height" : 480, "width" : 640, "tags" : [ "dogs", "sunrises", "work" ] }

    Each entry has 3 elements in tags  array.

Mongo Query and SQL query Side by Side - Part 1

Here we will try to write simple SQL queries & parallel Mongo Query.

First we need test database for that Download Northwind Dump:
    https://github.com/tmcnab/northwind-mongo/archive/master.zip

    extract and run shell script within.It will create Database called 'Northwind' in mongodb.

        bash mongo-import.sh

connect to mongo shell & switch to Northwind database.

>mongo
>use Northwind

check imported collections

>show collections;
Output:
    categories
    customers
    employee-territories
    northwind
    order-details
    orders
    products
    regions
    shippers
    suppliers
    territories


Queries:

1) Select all record but only few columns

    select CategoryID,CategoryName,field6 from categories

    db.categories.find({},{CategoryID:1,CategoryName:1,field6:1}).pretty();  

2) where clause
  a]
    select CategoryID,CategoryName from categories where CategoryID > 5

    db.categories.find({CategoryID: {$gt:5} },{CategoryID:1,CategoryName:1}).pretty();

  b]
  select CategoryID,CategoryName from categories where CategoryID > 5 and CategoryID < 8

  db.categories.find({$and:[{CategoryID: {$gt:5}},{CategoryID: {$lt:8}}]},{CategoryID:1,CategoryName:1}).pretty()

  c]
    select CategoryID,CategoryName from categories where CategoryID > 5 and CategoryID <= 8

    db.categories.find({$and:[{CategoryID: {$gt:5}},{CategoryID: {$lte:8}}]},{CategoryID:1,CategoryName:1}).pretty()

 

3) Like Clause
  a]  select CategoryID,CategoryName from categories where CategoryID > 5 and CategoryID <= 8 and CategoryName like '%ea%'
 
   db.categories.find({$and:[
        {CategoryID: {$gt:5}},
        {CategoryID: {$lte:8}},
        {CategoryName: /ea/ }    
      ]},{CategoryID:1,CategoryName:1}).pretty()  

  b]
    select CategoryID,CategoryName from categories where CategoryID > 5 and CategoryID <= 8 and CategoryName like 'Sea%'

    db.categories.find({$and:[
        {CategoryID: {$gt:5}},
        {CategoryID: {$lte:8}},
        {CategoryName: /^Sea/ }    
      ]},{CategoryID:1,CategoryName:1}).pretty()  

 c]
  select CategoryID,CategoryName from categories where CategoryID > 1 and CategoryID <= 8 and CategoryName like '%ts'

  db.categories.find({$and:[
        {CategoryID: {$gt:1}},
        {CategoryID: {$lte:8}},
        {CategoryName: /ts$/ }    
      ]},{CategoryID:1,CategoryName:1}).pretty()


3) Date Manipulation

a] get current date as computed column
        select OrderDate,now() from  orders

        db.orders.aggregate([{
        $project: {
            dt: new Date(),
            OrderDate:1
        }
        }])    

b] conversion of datetime string to iso datetime

    select
        OrderDate,
        to_char(TO_TIMESTAMP(OrderDate, 'YYYY-MM-DD HH24:MI:SS') at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as OrderDateISO
    from
        orders

    db.orders.aggregate( [ {
    $project: {
        OrderDateISO: {
            $dateFromString: {
                dateString: '$OrderDate',
                timezone: 'America/New_York'
            }
        },
        OrderDate:1
    }
    } ] )

    For mondodb version less than 3.6 , '$dateFromString' functionality is not supported.


Extracting Date Part:

   a) Substring
        db.orders.aggregate([{
            $project: {
                dateSubString: { $substr: [ "$OrderDate", 0, 10 ] },
                timeSubString: { $substr: [ "$OrderDate", 11, 8 ] }
            }
        }])      

    b) concatenation
 
    db.orders.aggregate(
        [
            {
                $project: {
                        OrderID:1,
                        ShipName :1,
                        ShipAddress : 1,
                        ShipCity : 1,
                        ShipRegion :1,
                        ShipPostalCode :1,
                        ShipCountry : 1,
                        ShipCountry : 1,
                        OrderDate:new ISODate()
                }
            }
        ]
    )

6) Making Copy of collection
    db.orders.aggregate([ { $match: {} }, { $out: "ordercopy" } ])  

    this will create new collection 'ordercopy' in same database which will hold same data as 'order'

7) Convert a string column to datetiem column

a]    Original structure of OrderCopy collection is like

    {
        "_id" : ObjectId("5a83be15f653cf290a966288"),
        "OrderID" : 10269,
        "CustomerID" : "WHITC",
        "EmployeeID" : 5,
        "OrderDate" : "1996-07-31 00:00:00.000",
        "RequiredDate" : "1996-08-14 00:00:00.000",
        "ShippedDate" : "1996-08-09 00:00:00.000",
        "ShipVia" : 1,
        "Freight" : 4.56,
        "ShipName" : "White Clover Markets",
        "ShipAddress" : "1029 - 12th Ave. S.",
        "ShipCity" : "Seattle",
        "ShipRegion" : "WA",
        "ShipPostalCode" : 98124,
        "ShipCountry" : "USA"
    }

    where OrderDate is string ,now we will convert this column to be of type date as follows

    var cursor = db.ordercopy.find();
    while (cursor.hasNext()) {
    var doc = cursor.next();
        db.ordercopy.update({_id : doc._id}, {$set : {OrderDate : new ISODate(doc.OrderDate) }});
    }

    After modification OrderCopy collection structure is as follows.

        {
            "_id" : ObjectId("5a83be15f653cf290a966288"),
            "OrderID" : 10269,
            "CustomerID" : "WHITC",
            "EmployeeID" : 5,
            "OrderDate" : ISODate("1996-07-31T00:00:00Z"),
            "RequiredDate" : "1996-08-14 00:00:00.000",
            "ShippedDate" : "1996-08-09 00:00:00.000",
            "ShipVia" : 1,
            "Freight" : 4.56,
            "ShipName" : "White Clover Markets",
            "ShipAddress" : "1029 - 12th Ave. S.",
            "ShipCity" : "Seattle",
            "ShipRegion" : "WA",
            "ShipPostalCode" : 98124,
            "ShipCountry" : "USA"
        }

b] Finding Record between two date

    select * from ordercopy where OrderDate between '1996-07-01' and '1996-08-01'
    
    db.ordercopy.find({
            OrderDate: {
                $gte: ISODate("1996-07-01T00:00:00Z"),
                $lt: ISODate("1996-08-01T00:00:00Z")
            }
        })

 8] Rename field
    Here we will rename field 'OrderDate' to 'order_date' in 'ordercopy' collection as follows

    db.ordercopy.update({}, {$rename: {"OrderDate": "order_date"}}, false, true);

    After Rename OrderCopy structure look like:

        {
            "_id" : ObjectId("5a83be15f653cf290a966288"),
            "OrderID" : 10269,
            "CustomerID" : "WHITC",
            "EmployeeID" : 5,
            "RequiredDate" : "1996-08-14 00:00:00.000",
            "ShippedDate" : "1996-08-09 00:00:00.000",
            "ShipVia" : 1,
            "Freight" : 4.56,
            "ShipName" : "White Clover Markets",
            "ShipAddress" : "1029 - 12th Ave. S.",
            "ShipCity" : "Seattle",
            "ShipRegion" : "WA",
            "ShipPostalCode" : 98124,
            "ShipCountry" : "USA",
            "order_date" : ISODate("1996-07-31T00:00:00Z")
        }

Tuesday, February 13, 2018

How to emit socket.io event from express route

To demonstrate emitting socket.io event inside express route ,
we will  create a chat application first & them from our express route we will emit clear event to all chats on client.

lets start with creating express.js app.

first install express generator

npm install express-generator -g

then

mkdir xpress002
cd xpress002

we will use express generator to create boilerplate code as

    express -e

 now install socket.io  package  as
    npm install socket.io --save

Creating client  involve creating view & javascript to connect to socket.io from client side & displaying it on html also we need to serve our view through route.

a) inside views create socketclient.ejs with following content

            <html>

            <head>
                <title>Real time web chat</title>
                <script src="/socket.io/socket.io.js"> </script>
                <script src="/static/javascripts/code.js"></script>
            </head>

            <body>

                Name:
                <input type="text" name="name" id="name" style='width:350px;' /><br/>
                Message:
                <input type="text" name="field" id="field" style='width:350px;' /><br/>

                <input type="button" name="send" id="send" value="send" />
                <div id="content" style='width: 500px; height: 300px; margin: 0 0 20px 0; border: solid 1px #999; overflow-y: scroll;'>

                </div>
                <input type="button" name="clear" id="clear" value="clear" />
            </body>

            </html>

b) inside public/javascripts create code.js as follows

    window.onload = function () {

        var messages = [];
        var socket = io.connect('http://localhost:3000');

        var field = document.getElementById("field");
        var sendButton = document.getElementById("send");
        var content = document.getElementById("content");
        var name = document.getElementById("name");
        var clearButton = document.getElementById("clear");

        socket.on('warmup', function (data) {
            messages = [];
            if (data.message) {
                messages.push(data);
                var html = '';
                for (var i = 0; i < messages.length; i++) {
                    html += '<b>' + (messages[i].username ? messages[i].username : 'Server') + ': </b>';
                    html += messages[i].message + '<br />';
                }
                content.innerHTML = html;
            } else {
                console.log("There is a problem:", data);
            }
        });

        socket.on('cleanup', function (data) {
            messages = [];
            content.innerHTML = '';     
        });

        socket.on('message', function (data) {
            if (data.message) {
                messages.push(data);
                var html = '';
                for (var i = 0; i < messages.length; i++) {
                    html += '<b>' + (messages[i].username ? messages[i].username : 'Server') + ': </b>';
                    html += messages[i].message + '<br />';
                }
                content.innerHTML = html;
            } else {
                console.log("There is a problem:", data);
            }
        });

        sendButton.onclick = function () {
            if (name.value == "") {
                alert("Please type your name!");
            } else {
                var text = field.value;
                socket.emit('send', { message: text, username: name.value });
            }
        };

        clearButton.onclick = function () {
            socket.emit('cleanup', {});
        };

    }

c) inside bin/www file just below
        var server = http.createServer(app);
   add

        var io = require('socket.io').listen(server);
        app.io = io;//to let io accessible in router object
        io.on('connection', function (socket) {
        socket.emit('warmup', { message: 'welcome to the chat using socket.io' });

        socket.on('send', function (data) {
        io.sockets.emit('message', data);
        });

        socket.on('cleanup', function () {
        io.sockets.emit('cleanup', '');
        });
        });

d)  inside routes  add socket.js with following content

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

        router.get('/foo', function (req, res) {
            req.app.io.sockets.emit('cleanup', {});
            res.end()
        });


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

        module.exports = router;

e) inside  app.js in root folder of application of express app make sure static point to public folder as
        app.use('/static', express.static(path.join(__dirname, 'public')));

        and route defined for socket included as  

        var socket = require('./routes/socket');
        app.use('/socket', socket);

my app.js file looks like

        var express = require('express');
        var path = require('path');
        var favicon = require('serve-favicon');
        var logger = require('morgan');
        var cookieParser = require('cookie-parser');
        var bodyParser = require('body-parser');

        var index = require('./routes/index');
        var users = require('./routes/users');
        var socket = require('./routes/socket');


        var app = express();

        // view engine setup
        app.set('views', path.join(__dirname, 'views'));
        app.set('view engine', 'ejs');

        // uncomment after placing your favicon in /public
        //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
        app.use(logger('dev'));
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: false }));
        app.use(cookieParser());
        app.use('/static', express.static(path.join(__dirname, 'public')));

        app.use('/', index);
        app.use('/users', users);
        app.use('/socket', socket);


        // catch 404 and forward to error handler
        app.use(function (req, res, next) {
        var err = new Error('Not Found');
        err.status = 404;
        next(err);
        });

        // error handler
        app.use(function (err, req, res, next) {
        // set locals, only providing error in development
        res.locals.message = err.message;
        res.locals.error = req.app.get('env') === 'development' ? err : {};

        // render the error page
        res.status(err.status || 500);
        res.render('error');
        });

        module.exports = app;

Run express app with

        DEBUG=xpres002:* npm start


Now to test socket.io chat application open below url in two tabs

    http://localhost:3000/socket/socketclient

    add some stuff inside name & message textbox and click 'send'.Message is delivered to both window,try it few times.now in third tab hit

    http://localhost:3000/socket/foo

    you will see both chat client message history is cleared.