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 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 Slide

  3. @jacdevos | @martincronje | @pgermishuys

    View Slide

  4. View Slide

  5. Begin

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. Why did we choose Node.js?

    View Slide

  13. 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 Slide

  14. Middle

    View Slide

  15. View Slide

  16. 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 Slide

  17. View Slide

  18. Mocha

    View Slide

  19. Dev Feedback Loop (OSX)

    View Slide

  20. 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 Slide

  21. Dev Feedback Loop (Win)

    View Slide

  22. 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 Slide

  23. 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 Slide

  24. Not type safe

    View Slide

  25. 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 Slide

  26. 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 Slide

  27. 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 Slide

  28. 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 Slide

  29. 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 Slide

  30. Passport

    View Slide

  31. 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 Slide

  32. View Slide

  33. View Slide

  34. Debugging

    View Slide

  35. Error Handling

    View Slide

  36. Hosting
    Free
    Free
    Free

    View Slide

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

    View Slide

  38.  
    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 Slide

  39. View Slide

  40. View Slide

  41. Fun community!

    View Slide

  42. View Slide

  43. View Slide