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. 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/
  2. 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)
  3. 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/');  
  4. 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  
  5. 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));          });  
  6. 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();  
  7. 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);      //...   });  
  8. 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);          }   };  
  9. 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);   });  
  10. 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  <resource>')          .description('Count  records  for  the  specified  resource.')          .option('-­‐i,  -­‐-­‐user  <user>',  ‘User  for  which  data  is  returned')          .option('-­‐u,  -­‐-­‐url  <url>',  'The  base  url  against  which  to  execute')          .action(function  (resource,  options)  {                    console.log(options.user);                  console.log(options.url);            });      
  11. 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);          });   });    
  12. 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));    
  13.   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                                        }                                  );