Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JSinSA 2014: Using node.js for a real app: The good, the bad and the ugly

JSinSA 2014: Using node.js for a real app: The good, the bad and the ugly

JSinSA 2014 talk:

In this session I will share our company's experiences we gained we building a production app using Node.js. The frameworks and libraries we used include express, passport, mongoose and many others. The talk will focus on the hard lessons, truly awesome moments of awe and those epic - but sometimes embarrassing - bug-hunts. Further we will also touch on subtler points like tools and project setup and deployment.

Martin Cronjé

May 31, 2014
Tweet

More Decks by Martin Cronjé

Other Decks in Programming

Transcript

  1. The good, the bad and the ugly of Node.js
    @nrealitydotcom
    [email protected]

    View full-size slide

  2. Copyright
    With the exception of external sourced and non-original material; the material in this
    training is licensed under Creative Commons Attribution-Noncommercial-No
    Derivative Works 2.5 South Africa.
    You are free:
    •  to Share — to copy, distribute and transmit the work
    Under the following conditions:
    •  Attribution — You must attribute the work in the manner specified by the author or
    licensor (but not in any way that suggests that they endorse you or your use of the
    work).
    •  Non-commercial — You may not use this work for commercial purposes.
    •  No Derivative Works — You may not alter, transform, or build upon this work.
    http://creativecommons.org/

    View full-size slide

  3. @jacdevos | @martincronje | @pgermishuys

    View full-size slide

  4. Why did we choose Node.js?

    View full-size slide

  5. Stack
    API
    Node.js
    •  Express
    •  Passport
    •  Mongoose
    •  Async
    •  Forky
    •  New Relic
    •  Authorizer
    •  JSON web token
    Client
    iOS
    Web
    (coming soon)
    Android
    (coming soon)
    Windows Phone
    (coming soon)
    CLI (Admin)
    Node.js
    •  Commander
    •  Prompt
    •  Colors
    •  Config
    •  Async
    •  Superagent
    •  MongoDB
    MongoDB
    Tests (Mocha, Expect, Should, Superagent)

    View full-size slide

  6. Learning
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
     
    //  Hello  world  
     
    var  http  =  require('http');  
     
    http.createServer(function  (req,  res)  {  
     
       res.writeHead(200,  {'Content-­‐Type':  'text/plain'});  
       res.end('Hello  World\n');  
     
    }).listen(1337,  '127.0.0.1');  
     
    console.log('Server  running  at  http://127.0.0.1:1337/');  

    View full-size slide

  7. Dev Feedback Loop (OSX)

    View full-size slide

  8. Dev Feedback Loop
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    #  ~/.tmuxinator/node_dev.yml  
    name:  lasser  
    root:  ~/Development/git/lasser/src  
    windows:  
       -­‐  editor:  vi  
       -­‐  tests:    
               layout:  main-­‐vertical  
               panes:  
                   -­‐  cd  node.api  &&  NODE_ENV=test  nodemon  app.js  
                   -­‐  mocha  -­‐R  spec  -­‐w  node.api/tests/  
       -­‐  shell:    
       -­‐  mongo:  
               layout:  main-­‐vertical  
               panes:  
                   -­‐  mongod  -­‐v  
                   -­‐  tail  -­‐f  /usr/local/var/log/mongodb/mongo.log  

    View full-size slide

  9. Dev Feedback Loop (Win)

    View full-size slide

  10. Superagent
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  superagent  =  require('superagent');  
    var  agent  =  superagent.agent();  
     
    agent.post(host  +  'http://localhost:4000/auth/local-­‐signup')  
           .send(user)  
           .end(function  (err,  res)  {  
     
                   if  (err)  
                           console.log(err.message);  
     
                   console.log('STATUS:    '  +  res.status);  
                   console.log('HEADERS:  '  +  JSON.stringify(res.headers));  
                   console.log('BODY:        '  +  JSON.stringify(res.body));  
           });  

    View full-size slide

  11. Superagent (without)
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  http  =  require('http');  
    var  options  =  {  
           hostname:  'localhost',  port:  4000,  path:  '/api/auth/local-­‐signup',  
           method:  'POST',  
           headers:  {'content-­‐type':  'application/json'}  
    };  
    var  req  =  http.request(options,  function  (res)  {  
           console.log('STATUS:  '  +  res.statusCode);  
           console.log('HEADERS:  '  +  JSON.stringify(res.headers));  
           res.on('data',  function  (chunk)  {  console.log('BODY:  '  +  chunk);});  
    });  
    req.on('error',  function  (err)  {  
           console.log(err.message);  
    });  
    req.write(JSON.stringify({/*  data  here  */}));  
    req.end();  

    View full-size slide

  12. Not type safe

    View full-size slide

  13. Connect style middleware
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
     
    app.configure(function()  {  
       //...  
       app.use(require('morgan')('dev'));  
       app.use(require('body-­‐parser')());  
       app.use(require('cookie-­‐parser')());    
       app.use(require('express-­‐session')({  secret:  '1337'  }));    
       //...  
       app.use(passport.initialize());  
       app.use(passport.session());  
       //...      
       app.use(authorizer(map));  
       app.use(metaDataLogger);  
       //...  
    });  

    View full-size slide

  14. Mongoose (schema)
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  mongoose  =  require('mongoose');  
     
    exports  =  module.exports  =  function(app,  mongoose)  {  
     
           var  tripCardSchema  =  mongoose.Schema({  
                   name:  {type:  String,  required:  true},  
                   startLocation:  {type:  [Number],  index:  '2d'},  
                   isAtlassingOn:  {type:  Boolean,  default:  false},  
                   userId:  {type  :  Schema.Types.ObjectId,  required:  true},  
                   lastUpdated  :  {type:  Date,  default:  Date.now,  required  :  true},  
                   archived:  {type:  Boolean,  default:  false},  
           });  
           if  (!mongoose.connection.models.TripCard)  {  
                   mongoose.connection.model('TripCard',  tripCardSchema);  
           }  
    };  

    View full-size slide

  15. Mongoose usage
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  mongoose  =  require('mongoose');  
    var  schema  =  mongoose.connection.models.TripCard;  
     
    schema.find()  
           .where('lastUpdated').gte(query.changedSince)  
           .where('userId').equals(req.user._id);  
           .exec(function  (err,  results)  {  
                   if  (handleError(err,  res))  return;  
                   res.send(results);  
    });  
     
    var  model  =  new  me.schema(req.body);  
    model.save(function  (err,  saved)  {  
           if  (handleError(err,  res))  return;  
           res.send(saved);  
    });  

    View full-size slide

  16. Command line interface (Host)
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  program  =  require('commander');  
     
    program  
           .command('count  ')  
           .description('Count  records  for  the  specified  resource.')  
           .option('-­‐i,  -­‐-­‐user  ',  ‘User  for  which  data  is  returned')  
           .option('-­‐u,  -­‐-­‐url  ',  'The  base  url  against  which  to  execute')  
           .action(function  (resource,  options)  {  
     
                   console.log(options.user);  
                   console.log(options.url);  
     
           });  
     
     

    View full-size slide

  17. Command line interface (Test)
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  clt  =  require('clt');  
    var  admin  =  __dirname  +  '/../../ladmin  ';  
     
    describe('Count',  function  ()  {  
           it('should  return  the  species  count',  function  (done)  {  
                   cli()  
                           .use(admin  +  'count  species')  
                           .expect('record(s)  on  the  server')  
                           .expect(0,  done);  
           });  
    });  
     

    View full-size slide

  18. Authorisation
    001  
    002  
    003  
    004  
    005  
    006  
    007  
    008  
    009  
    010  
    011  
    012  
    013  
    014  
    015  
    016  
     
    var  authorizer  =  require('authorizer');  
     
    var  routes  =  [  
       {  method:  'delete',  path:  '/api*',  check:  isAdmin  },  
       {  method:  'post',  path:  '/api/resource',  check:  isAdmin},  
       {  method:  'post',  path:  '/api/auth*',  check:  authorizer.assertAlwaysOpen},
       {  method:  'get,post',  path:  '/api*',  check:  isAuthenticated},  
       {  method:  '*',  path:  '*',  check:  authorizer.assertAlwaysClosed}  
    ];  
     
    app.use(authorizer(routes));  
     

    View full-size slide

  19. Error Handling

    View full-size slide

  20. Hosting
    Free
    Free
    Free

    View full-size slide

  21. Callbacks
    function  someFunction()  {  
           console.log('Hello');  
    };

    View full-size slide

  22.  
    FieldSheetConverter.prototype.convertToSabap2  =  function(fieldSh
           if(fieldSheet.classification  ===  'incidental'){  
                   async.series([  
                           function  populateIncidentalRecord(complete)  {  
                                   var  xml  =  builder.create('NewDataSet',  {  standal
                                   async.eachSeries(  
                                           fieldSheet.tripRecords,  
                                           function  populateRecord(tripRecord,  complete
                                                   app.db.models.Species.findOne({  _id:  tri
                                                           function  (err,  species)  {  
                                                                 var  incidentalElement  =  xml.ele('
                                                                 incidentalElement.ele('SightingDa
                                                                 complete();  
                                                   });  
                                           },  
                                           function  completed(err)  {  
                                                   complete(err,  {  name:  'xml_incid.xml',  c
                                           }  
                                   );  
     

    View full-size slide

  23. Fun community!

    View full-size slide