Express vs. Hapi – Injection de dépendances



Express vs. Hapi – Injection de dépendances

0 0


nodejsparis-express-hapi

Talk node.js paris sur express vs. hapi

On Github Floby / nodejsparis-express-hapi

Express vs. Hapi

for APIs with Node.js

Hello world, notes

Qui sommes-nous ?

Florent Jaby

  • Consultant pour OCTO Technology
  • Noder depuis 2009
  • @Floby sur Github et le reste d'internet

Adrien Becchis

  • En stage chez OCTO Technology
  • Noder depuis 2015 =)
  • @AdrieanKhisbe sur Github

Pourquoi Node.js ?

  • Itérations rapide sur le code
  • JSON natif
  • Asynchrone
  • Écosystème riche (npm)
  • Parce que c'est cool !

Quelques stats

Métriques Express Hapi Github stars 19k 4,2k Github fork 3,6k 0,6k StackOverflow 15k 180 Contributor 177 114 Github require ~360k 6k First Commit 26 Juin 2009 6 Août 2011

Fonctionnement d'Express

Cycle de vie d'une requête HTTP avec Express Echainement des middlewares

Fonctionnement de Hapi

Cycle de vie d'une requête HTTP avec Hapi "Hooks" disponible en Hapi

Exemple CRUD Express

var express = require('express');
var app = express();
var MeetupDAO = require(somePath)(someConfiguration);

app.get('/meetups', function (req, res) {
    MeetupDAO.all(function (err, meetups) {
        if(err) res.sendStatus(500);
        else res.json(meetups);
    });
});
// autres routes CRUD: collection.post, member.get,put,delete .

var server = app.listen(3000, function () {
    var host = server.address().address;
    var port = server.address().port;
    console.log('Example app listening at http://%s:%s', host, port);
});
MiddleWare
var bodyParser = require('body-parser');
var morgan = require('morgan');

// loading
app.use(bodyParser.json());
app.use(morgan('combined'));

// declaration
function logger(request, response, next) {
    var start = +new Date();
    var url = request.url;
    var method = request.method;

    response.on('finish', function() {
          var duration = +new Date() - start;
        console.log(method + ' ' + url +' (' + duration + ')');
    });
    next();
}
Routes
app.route('/meetups')
    .get(MeetupController.all)
    .post(MeetupController.create);

app.route('/meetups/:id')
    .get(MeetupController.get)
    .put(MeetupController.update)
    .patch(MeetupController.partialUpdate)
    .delete(MeetupController.remove);
Controllers
function all(req, res) {
    MeetupDAO.all(function (all) {
        res.json(all);
    });
}

function create(req, res) {
    if (!req.body.url) {
        return res.sendStatus(400);
    }
    var meetup = Meetup.create(req.body.url, req.body.title, req.body.date);

    MeetupDAO.save(meetup, function (newMeetup) {
        res.location(meetupsEndpoint + '/' + newMeetup._id);
        res.status(201).json({});
    });
}
Error handling
var StandardError = require('standard-error');

app.use(function (err, req, res, next) {
  if (err instanceof IKnowThisError) {
    res.status(503).json({
      error: 'service_unavaible',
      reason: 'Looks like one of our partners is not doing his job'
    });
  } else {
    next(err);
  }
});

app.use(function (err, req, res, next) {
  if (err instanceof StandardError) {
    res.status(err.code);
    res.json(error.toJSON());
  } else {
    next(err);
  }
});

Exemple CRUD Hapi

var MeetupDAO = require(somePath)(someConfiguration);

var server = new Hapi.Server({ load: {sampleInterval: 5000}});
server.connection({port: 3000});

server.route([
    { method: 'GET',
      path: '/meetups',
      handler: function (request, reply) {
          MeetupDAO.all(function (err, meetups) {
              if(err) reply().code(500);
              else reply(meetups);
          });
      }
    }
    // autres routes.
]);

server.start(function () {
    console.log('Server running at:', server.info.uri);
});
Routes
var meetupRoutes = [
    { method: 'GET', path: '/meetups', handler: MeetupController.all },
    {
      method: 'POST', path: '/meetups',
      config: {
          handler: MeetupController.create,
          validate: {payload: schemas.meetupCreate}
      }
    },
    {
      method: 'PUT', path: '/meetups/{id}',
      config: {
          handler: MeetupController.update,
          validate: { payload: schemas.meetupUpdate }
      }
    },
    { method: 'DELETE', path: '/meetups/{id}', handler: MeetupController.remove },
    { method: 'GET', path: '/meetups/{id}', handler: MeetupController.get },
];
Controllers
function all(request, reply) {
    MeetupDAO.all(function (all) {
        reply(all);
    });
}
function  create(request, reply) {
    if (!request.payload.url) {
        return reply().code(400);
    }
    var meetup = Meetup.create(request.payload.url,
                               request.payload.title, request.payload.date);

    MeetupDAO.save(meetup, function (newMeetup) {
        reply().created(meetupsEndpoint + '/' + newMeetup._id);
    });
}
Error handling
var Boom = require('boom');
//....
server.route({
    method: 'GET',  path: '/meetups/{id}',
    handler: function getMeetup(request, reply) {
        if (!request.params.id) {
            return reply(Boom.badRequest('No id was provided'));
        }
        MeetupDAO.get(request.params.id, function (err, meetup) {
            if(err) reply(Boom.wrap(err, 500));
            else reply(meetup);
        });
    }
});

Injection de dépendances

  • "Inversion de contrôle"
  • Ne pas instancier ses classes de délégation dans le code qui les utilise
Express
app.set('db', someDbConnection) // injecte 'db' dans 'app'
app.use(bodyparse.json()); // injecte `.body` dans `req`
app.use(cookieparser()); // injecte `.cookies` dans `req`
app.use(session()); // injecte `.session` dans `req`

app.use(function currentUser(req, res, next) {
  var userId = req.session.userId;
  if (!userId) return next();
  app.get('db').users.findOne({_id: userId}, function (err, user) {
    if (err) return next(err);
    req.currentUser = user // injection de l'utilsateur courant
  });
});
Hapi
server.bind({db: someDbConnection});
server.register({
  // injecte `.session` sur l'objet `.request`
  register: require('yar')
})

// ...
{
  method: 'GET',
  path: '/current-user',
  handler: function (request, reply) {
    this.db.users.findOne({_id: request.session.userId}, function (err, user) {
      if (err) return reply(err);
      reply(user);
    })
  }
}

Conclusion

Express
  • old-timer
  • "lightweight", "thin", "lean", bref : minimal
  • Incrément et itérations rapides au début
  • Perte de robustesse et de maintenabilité au fil du temps
Hapi
  • new player
  • "Vrai" framework qui apporte ses conventions et abstractions
  • Courbe d'apprentissage plus forte
  • Plus industriel, plus compréhensible pour les dev non-js

Tooling

Express
  • 20k modules npm
  • Les outils StrongLoop
  • n'importer quel outil unix ;)
Hapi
Merci !

have some more links

Dépot Github du comparatif

Pourquoi utiliser Node pour réaliser mon API ?

Architecture et Ecosystème d’Express et Hapi

Express et Hapi en pratique