Comment Express et hapi se comparent-ils?

133

Du point de vue de la conception et du développement d'applications Web, comment Express et Hapi se comparent-ils? Pour les exemples de base, ils semblent similaires, mais je suis intéressé à en savoir plus sur les différences clés dans la structure globale de l'application.

Par exemple, pour autant que je l' ai appris, Hapi utilise un autre mécanisme de routage qui ne prend pas l' ordre d'inscription en compte, peut faire des recherches plus rapides, mais il est limité Comparant à Express. Y a-t-il d'autres différences importantes?

Il y a aussi un article sur le choix de Hapi (plutôt qu'Express) pour développer le nouveau site Web npmjs.com, cet article indique que "le système de plugins de Hapi signifie que nous pouvons isoler différentes facettes et services de l'application de manière à permettre des microservices dans le Express, par contre, nécessite un peu plus de configuration pour obtenir les mêmes fonctionnalités », qu'est-ce que cela signifie exactement?

Ali Shakiba
la source

Réponses:

231

C'est une grande question et nécessite une longue réponse pour être complète, donc je vais simplement aborder un sous-ensemble des différences les plus importantes. Toutes mes excuses, c'est encore une longue réponse.

En quoi sont-ils similaires?

Vous avez absolument raison quand vous dites:

Pour des exemples de base, ils semblent similaires

Les deux frameworks résolvent le même problème de base: fournir une API pratique pour créer des serveurs HTTP dans le nœud. C'est-à-dire plus pratique que d'utiliser httpseul le module natif de niveau inférieur . Le httpmodule peut faire tout ce que nous voulons mais c'est fastidieux d'écrire des applications avec.

Pour ce faire, ils utilisent tous les deux des concepts qui existent depuis longtemps dans les frameworks Web de haut niveau: routage, gestionnaires, plugins, modules d'authentification. Ils n'ont peut-être pas toujours eu les mêmes noms, mais ils sont à peu près équivalents.

La plupart des exemples de base ressemblent à ceci:

  • Créer un itinéraire
  • Exécuter une fonction lorsque l'itinéraire est demandé, préparer la réponse
  • Répondre à la demande

Express:

app.get('/', function (req, res) {

    getSomeValue(function (obj) {

        res.json({an: 'object'});
    });
});

hapi:

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        getSomeValue(function (obj) {

            reply(obj);
        });
    }
});

La différence n'est pas vraiment révolutionnaire ici, non? Alors pourquoi choisir l'un plutôt que l'autre?

Comment sont-ils différents?

La réponse simple est que hapi est beaucoup plus et il fait beaucoup plus prêt à l'emploi. Cela peut ne pas être clair si vous regardez simplement l'exemple ci-dessus. En fait, c'est intentionnel. Les cas simples restent simples. Examinons donc quelques-unes des grandes différences:

Philosophie

Express est conçu pour être très minimal. En vous donnant une petite API avec juste une fine couche de poussière en plus http, vous êtes toujours très seul en termes d'ajout de fonctionnalités supplémentaires. Si vous souhaitez lire le corps d'une requête entrante (tâche assez courante), vous devez installer un module séparé . Si vous vous attendez à ce que divers types de contenu soient envoyés vers cette route, vous devez également vérifier l'en- Content-typetête pour vérifier de quoi il s'agit et l'analyser en conséquence (form-data vs JSON vs multi-part par exemple), souvent en utilisant des modules séparés .

hapi dispose d'un riche ensemble de fonctionnalités, souvent exposé via des options de configuration, plutôt que d'exiger l'écriture de code. Par exemple, si nous voulons nous assurer qu'un corps de requête (charge utile) est entièrement lu en mémoire et correctement analysé (automatiquement en fonction du type de contenu) avant que le gestionnaire ne soit exécuté, il s'agit simplement d'une option simple :

server.route({
    config: {
        payload: {
            output: 'data',
            parse: true
        }
    },
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        reply(request.payload);
    }
});

Caractéristiques

Il vous suffit de comparer la documentation de l'API sur les deux projets pour voir que hapi offre un plus grand ensemble de fonctionnalités.

hapi inclut certaines des fonctionnalités intégrées suivantes qu'Express ne propose pas (pour autant que je sache):

Extensibilité et modularité

hapi et Express abordent l'extensibilité d'une manière tout à fait différente. Avec Express, vous disposez de fonctions middleware . Les fonctions middleware sont un peu comme des filtres que vous empilez et toutes les requêtes les traversent avant de toucher votre gestionnaire.

hapi a le cycle de vie de la requête et offre des points d'extension , qui sont comparables aux fonctions middleware mais existent à plusieurs points définis dans le cycle de vie de la requête.

L'une des raisons pour lesquelles Walmart a créé hapi et a cessé d'utiliser Express était la frustration de la difficulté à diviser une application Express en plusieurs parties et à faire travailler différents membres de l'équipe en toute sécurité sur leur partie. Pour cette raison, ils ont créé le système de plugins dans hapi.

Un plugin est comme une sous-application, vous pouvez faire tout ce que vous pouvez dans une application hapi, ajouter des itinéraires, des points d'extensions etc. Dans un plugin, vous pouvez être sûr que vous ne cassez pas une autre partie de l'application, car l'ordre de les enregistrements d'itinéraires n'ont pas d'importance et vous ne pouvez pas créer d'itinéraires conflictuels. Vous pouvez ensuite combiner ces plugins dans un serveur et le déployer.

Écosystème

Parce qu'Express vous donne si peu de choses hors de la boîte, vous devez regarder à l'extérieur lorsque vous avez besoin d'ajouter quelque chose à votre projet. La plupart du temps, lorsque vous travaillez avec hapi, la fonctionnalité dont vous avez besoin est soit intégrée, soit un module créé par l'équipe principale.

Minimal sonne bien. Mais si vous créez une application de production sérieuse, il y a de fortes chances que vous ayez besoin de tout cela par la suite.

Sécurité

hapi a été conçu par l'équipe de Walmart pour gérer le trafic du Black Friday, de sorte que la sécurité et la stabilité ont toujours été une préoccupation majeure. Pour cette raison, le framework fait beaucoup de choses supplémentaires, comme limiter la taille de la charge utile entrante pour éviter d'épuiser la mémoire de votre processus. Il a également des options pour des choses comme le délai maximal de boucle d'événements, la mémoire RSS maximale utilisée et la taille maximale du tas v8, au-delà de laquelle votre serveur répondra avec un délai d'expiration de 503 plutôt que de simplement planter.

Résumé

Évaluez-les tous les deux vous-même. Pensez à vos besoins et lequel des deux répond à vos plus grandes préoccupations. Faites un plongeon dans les deux communautés (IRC, Gitter, Github), voyez laquelle vous préférez. Ne me croyez pas sur parole. Et bon piratage!


DISCLAIMER: Je suis partial en tant qu'auteur d'un livre sur hapi et ce qui précède est en grande partie mon opinion personnelle.

Matt Harrison
la source
7
Matt, merci pour le long article, les sections «extensibilité et modularité» et «sécurité» ont été les sections les plus utiles pour moi. Je suppose qu'il vaut la peine de mentionner que le nouveau système de routage d'Express 4 offre une modularité améliorée pour les sous-applications.
Ali Shakiba
1
Bonne réponse Matt. Nous sommes également confus entre Hapi et Express, un inconvénient que nous constatons avec Hapi est qu'il ne dispose pas d'un support communautaire aussi étendu qu'Express et pourrait être un problème majeur si nous restons coincés quelque part. Besoin de votre avis à ce sujet.
Aman Gupta
1
Express est générique, tandis que hapi est un peu plus d'entreprise.
windmaomao
1
@MattHarrison excellente réponse, en ce moment je lis votre livre sur Hapi, c'est tout simplement génial. Je suis sur le point de développer un nouveau marché pour les livres utilisant Hapi en backend et vue.js en frontend, après m'être habitué à Hapi, j'aimerais participer activement au projet Hapi.
Humoyun Ahmad
1
@Humoyun Super! Sachez cependant qu'il existe une nouvelle version majeure d'hapi avec quelques changements considérables depuis <= v16.0.0. Je produis actuellement une série de screencast conçue pour que les gens apprennent la v17: youtube.com/playlist?list=PLi303AVTbxaxqjaSWPg94nccYIfqNoCHz
Matt Harrison
54

Mon organisation accompagne Hapi. C'est pourquoi nous l'aimons.

Hapi c'est:

  • Soutenu par de grands corps. Cela signifie que le soutien de la communauté sera solide et disponible pour vous tout au long des prochaines versions. Il est facile de trouver des Hapi passionnés, et il existe de bons tutoriels (mais pas aussi nombreux et tentaculaires que les tutoriels ExpressJs). À partir de cette date, npm et Walmart utilisent Hapi.
  • Il peut faciliter le travail des équipes distribuées travaillant sur différentes parties des services backend sans avoir à avoir une connaissance approfondie du reste de la surface de l'API (l'architecture des plugins de Hapi est la quintessence de cette qualité).
  • Laissez le framework faire ce qu'un framework est censé faire: configurer les choses. Après cela, le cadre devrait être invisible et permettre aux développeurs de concentrer leur véritable énergie créative sur l'élaboration d'une logique métier. Après avoir utilisé Hapi pendant un an, je pense vraiment que Hapi accomplit cela. Je suis heureux!

Si vous voulez entendre directement Eran Hammer (leader de Hapi)

Au cours des quatre dernières années, hapi est devenu le cadre de choix pour de nombreux projets, petits ou grands. Ce qui rend hapi unique, c'est sa capacité à évoluer vers de grands déploiements et de grandes équipes. Au fur et à mesure qu'un projet se développe, sa complexité augmente également - complexité de l'ingénierie et complexité des processus. L'architecture et la philosophie d'hapi gèrent la complexité accrue sans qu'il soit nécessaire de constamment refactoriser le code [en savoir plus]

Commencer avec Hapi ne sera pas aussi facile que les ExpressJ car Hapi n'a pas le même "pouvoir d'étoile" ... mais une fois que vous vous sentirez à l'aise, vous obtiendrez BEAUCOUP de kilométrage. Cela m'a pris environ 2 mois en tant que nouveau hacker qui a utilisé de manière irresponsable ExpressJs pendant quelques années. Si vous êtes un développeur backend chevronné, vous saurez comment lire la documentation, et vous ne le remarquerez probablement même pas.

Domaines sur lesquels la documentation Hapi peut améliorer:

  1. comment authentifier les utilisateurs et créer des sessions
  2. Gestion des demandes d’origine croisée (CORS)
  3. téléchargement de fichiers (en plusieurs parties, en morceaux)

Je pense que l'authentification serait la partie la plus difficile car vous devez décider de la stratégie d'authentification à utiliser (authentification de base, cookies, jetons JWT, OAuth). Bien que ce ne soit techniquement pas le problème de Hapi que le paysage des sessions / authentification soit si fragmenté ... mais je souhaite qu'ils fournissent une certaine prise en main pour cela. Cela augmenterait considérablement le bonheur des développeurs.

Les deux autres ne sont pas vraiment si difficiles, les documents pourraient être rédigés légèrement mieux.

wle8300
la source
3

Quelques faits sur Hapi ou pourquoi Hapi JS?

Hapi est centré sur la configuration Il a l'authentification et l'autorisation intégrées dans le cadre Il a été publié dans une atmosphère testée au combat et a vraiment fait ses preuves Tous les modules ont une couverture de test à 100% Il enregistre le plus haut niveau d'abstraction loin du noyau HTTP Facilement compilable via l'architecture du plugin

Hapi est un meilleur choix en termes de performances Hapi utilise un mécanisme de routage différent, qui peut effectuer des recherches plus rapides et prendre en compte l'ordre d'enregistrement. Néanmoins, il est assez limité par rapport à Express. Et grâce au système de plugins Hapi, il est possible d'isoler les différentes facettes et services qui aideraient l'application à bien des égards à l'avenir.

Usage

Hapi est le framework le plus préféré par rapport à Express. Hapi est principalement utilisé pour les applications d'entreprise à grande échelle.

Voici quelques raisons pour lesquelles les développeurs ne choisissent pas Express lors de la création d'applications d'entreprise:

Les itinéraires sont plus difficiles à composer dans Express

L'intergiciel gêne la plupart du temps; chaque fois que vous définissez les itinéraires, vous devez écrire autant de nombres de codes.

Hapi serait le meilleur choix pour un développeur cherchant à créer une API RESTful. Hapi a une architecture de micro-services et il est également possible de transférer le contrôle d'un gestionnaire à un autre en fonction de certains paramètres. Avec le plugin Hapi, vous pouvez profiter d'un plus grand niveau d'abstraction autour de HTTP car vous pouvez diviser la logique métier en morceaux facilement gérables.

Un autre énorme avantage de Hapi est qu'il fournit des messages d'erreur détaillés lorsque vous vous trompez de configuration. Hapi vous permet également de configurer la taille de téléchargement de votre fichier par défaut. Si la taille de téléchargement maximale est limitée, vous pouvez envoyer un message d'erreur à l'utilisateur indiquant que la taille du fichier est trop grande. Cela empêcherait votre serveur de planter car les téléchargements de fichiers n'essaieront plus de mettre en mémoire tampon un fichier entier.

  1. Tout ce que vous pouvez réaliser en utilisant express peut également être facilement réalisé en utilisant hapi.js.

  2. Hapi.js est très élégant et organise très bien le code. Si vous voyez comment il effectue le routage et met la logique de base dans les contrôleurs, vous allez adorer.

  3. Hapi.js fournit officiellement plusieurs plugins exclusivement pour hapi.js, allant de l'authentification basée sur les jetons à la gestion de session et bien d'autres encore, qui est une publicité sur. Cela ne signifie pas que le npm traditionnel ne peut pas être utilisé, ils sont tous pris en charge par hapi.js

  4. Si vous codez dans hapi.js, un code serait très maintenable.

user9961607
la source
"Si vous voyez comment il fait le routage et met la logique de base dans les contrôleurs ...". Je ne vois aucun exemple dans la documentation qui montre l'utilisation de contrôleurs. Tous les exemples de routage utilisent la propriété du gestionnaire qui est une fonction. Je compare cette façon avec ce que font Laravel (framework PHP) et AdonisJs (framework Node.js) pour le routage dans lequel nous pouvons utiliser des contrôleurs pour le routage. J'ai probablement manqué des parties de la documentation HAPI qui montrent l'utilisation de contrôleurs pour le routage. Donc, si cette fonctionnalité existe, ce sera bien pour moi, car j'ai l'habitude d'utiliser des contrôleurs pour le routage dans Laravel.
Lex Soft
1

J'ai commencé à utiliser Hapi récemment et j'en suis assez content. Mes raisons sont

  1. Plus facile à tester. Par exemple:

    • server.inject vous permet d'exécuter l'application et d'obtenir une réponse sans qu'elle ne s'exécute et n'écoute.
    • server.info donne l'URI actuel, le port, etc.
    • server.settingsaccède à la configuration, par exemple server.settings.cacheobtient le fournisseur de cache actuel
    • en cas de doute, regardez les /testdossiers de n'importe quelle partie de l'application ou des plugins pris en charge pour voir des suggestions sur la façon de simuler / tester / stub, etc.
    • mon sentiment est que le modèle architectural de hapi vous permet de faire confiance mais de vérifier par exemple Mes plugins sont-ils enregistrés ? Comment puis-je déclarer une dépendance de module ?
  2. Cela fonctionne hors de la boîte, par exemple les téléchargements de fichiers , les flux de retour des points de terminaison, etc.

  3. Les plugins essentiels sont maintenus avec la bibliothèque principale. par exemple , analyse de modèle , mise en cache, etc. L'avantage supplémentaire est que les mêmes normes de codage sont appliquées à tous les éléments essentiels.

  4. Erreurs saines et gestion des erreurs. Hapi valide les options de configuration et conserve une table de routage interne pour éviter les routes dupliquées. Ceci est très utile lors de l'apprentissage car les erreurs sont générées tôt au lieu de comportements inattendus qui nécessitent un débogage.

Peter
la source
-1

Juste un autre point à ajouter, Hapi a commencé à prendre en charge les appels 'http2' à partir de la version 16 (si je ne me trompe pas). Cependant, express n'a pas encore pris en charge le module 'http2' directement jusqu'à express 4. Bien qu'ils aient publié la fonctionnalité dans la version alpha d'express 5.

UchihaItachi
la source
-2
'use strict';
const Hapi = require('hapi');
const Basic = require('hapi-auth-basic');
const server = new Hapi.Server();
server.connection({
    port: 2090,
    host: 'localhost'
});


var vorpal = require('vorpal')();
const chalk = vorpal.chalk;
var fs = require("fs");

var utenti = [{
        name: 'a',
        pass: 'b'
    },
    {
        name: 'c',
        pass: 'd'
    }
];

const users = {
    john: {
        username: 'john',
        password: 'secret',
        name: 'John Doe',
        id: '2133d32a'
    },
    paul: {
        username: 'paul',
        password: 'password',
        name: 'Paul Newman',
        id: '2133d32b'
    }
};

var messaggi = [{
        destinazione: 'a',
        sorgente: 'c',
        messsaggio: 'ciao'
    },
    {
        destinazione: 'a',
        sorgente: 'c',
        messsaggio: 'addio'
    },
    {
        destinazione: 'c',
        sorgente: 'a',
        messsaggio: 'arrivederci'
    }
];

var login = '';
var loggato = false;

vorpal
    .command('login <name> <pass>')
    .description('Effettua il login al sistema')
    .action(function (args, callback) {
        loggato = false;
        utenti.forEach(element => {
            if ((element.name == args.name) && (element.pass == args.pass)) {
                loggato = true;
                login = args.name;
                console.log("Accesso effettuato");
            }
        });
        if (!loggato)
            console.log("Login e Password errati");
        callback();
    });

vorpal
    .command('leggi')
    .description('Leggi i messaggi ricevuti')
    .action(function (args, callback) {
        if (loggato) {
            var estratti = messaggi.filter(function (element) {
                return element.destinazione == login;
            });

            estratti.forEach(element => {
                console.log("mittente : " + element.sorgente);
                console.log(chalk.red(element.messsaggio));
            });
        } else {
            console.log("Devi prima loggarti");
        }
        callback();
    });

vorpal
    .command('invia <dest> "<messaggio>"')
    .description('Invia un messaggio ad un altro utente')
    .action(function (args, callback) {
        if (loggato) {
            var trovato = utenti.find(function (element) {
                return element.name == args.dest;
            });
            if (trovato != undefined) {
                messaggi.push({
                    destinazione: args.dest,
                    sorgente: login,
                    messsaggio: args.messaggio
                });
                console.log(messaggi);
            }
        } else {
            console.log("Devi prima loggarti");
        }
        callback();
    });

vorpal
    .command('crea <login> <pass>')
    .description('Crea un nuovo utente')
    .action(function (args, callback) {
        var trovato = utenti.find(function (element) {
            return element.name == args.login;
        });
        if (trovato == undefined) {
            utenti.push({
                name: args.login,
                pass: args.pass
            });
            console.log(utenti);
        }
        callback();
    });

vorpal
    .command('file leggi utenti')
    .description('Legge il file utenti')
    .action(function (args, callback) {
        var contents = fs.readFileSync("utenti.json");
        utenti = JSON.parse(contents);
        callback();
    });

vorpal
    .command('file scrivi utenti')
    .description('Scrive il file utenti')
    .action(function (args, callback) {
        var jsontostring = JSON.stringify(utenti);
        fs.writeFile('utenti.json', jsontostring, function (err) {
            if (err) {
                return console.error(err);
            }
        });
        callback();
    });

vorpal
    .command('file leggi messaggi')
    .description('Legge il file messaggi')
    .action(function (args, callback) {
        var contents = fs.readFileSync("messaggi.json");
        messaggi = JSON.parse(contents);
        callback();
    });

vorpal
    .command('file scrivi messaggi')
    .description('Scrive il file messaggi')
    .action(function (args, callback) {
        var jsontostring = JSON.stringify(messaggi);
        fs.writeFile('messaggi.json', jsontostring, function (err) {
            if (err) {
                return console.error(err);
            }
        });
        callback();
    });

// leggi file , scrivi file

vorpal
    .delimiter(chalk.yellow('messaggi$'))
    .show();




const validate = function (request, username, password, callback) {
    loggato = false;


    utenti.forEach(element => {
        if ((element.name == username) && (element.pass == password)) {
            loggato = true;
            console.log("Accesso effettuato");
            return callback(null, true, {
                name: username
            })
        }
    });
    if (!loggato)
        return callback(null, false);
};

server.register(Basic, function (err) {
    if (err) {
        throw err;
    }
});

server.auth.strategy('simple', 'basic', {
    validateFunc: validate
});



server.route({
    method: 'GET',
    path: '/',
    config: {
        auth: 'simple',
        handler: function (request, reply) {
            reply('hello, ' + request.auth.credentials.name);
        }
    }
});

//route scrivere
server.route({
    method: 'POST',
    path: '/invia',
    config: {
        auth: 'simple',
        handler: function (request, reply) {
            //console.log("Received POST from " + request.payload.name + "; id=" + (request.payload.id || 'anon'));
            var payload = encodeURIComponent(request.payload)
            console.log(request.payload);
            console.log(request.payload.dest);
            console.log(request.payload.messaggio);
            messaggi.push({
                destinazione: request.payload.dest,
                sorgente: request.auth.credentials.name,
                messsaggio: request.payload.messaggio
            });
            var jsontostring = JSON.stringify(messaggi);
            fs.writeFile('messaggi.json', jsontostring, function (err) {
                if (err) {
                    return console.error(err);
                }
            });
            console.log(messaggi);
            reply(messaggi[messaggi.length - 1]);

        }
    }
});


//route leggere (json)
server.route({
    method: 'GET',
    path: '/messaggi',
    config: {
        auth: 'simple',
        handler: function (request, reply) {
            messaggi = fs.readFileSync("messaggi.json");
            var estratti = messaggi.filter(function (element) {
                return element.destinazione == request.auth.credentials.name;
            });
            var s = [];

            console.log(request.auth.credentials.name);
            console.log(estratti.length);
            estratti.forEach(element => {

                s.push(element);

                //fare l'array con stringify
                //s+="mittente : "+element.sorgente+": "+element.messsaggio+"\n";

            });
            var a = JSON.stringify(s);
            console.log(a);
            console.log(s);
            reply(a);
        }
    }
});



server.start(function () {
    console.log('Hapi is listening to ' + server.info.uri);
});

function EseguiSql(connection, sql, reply) {
    var rows = [];
    request = new Request(sql, function (err, rowCount) {
        if (err) {
            console.log(err);
        } else {
            console.log(rowCount + ' rows');
            console.log("Invio Reply")
            reply(rows);
        }
    });

    request.on('row', function (columns) {
        var row = {};
        columns.forEach(function (column) {
            row[column.metadata.colName] = column.value;
        });
        rows.push(row);
    });

    connection.execSql(request);
}

server.route({
    method: 'POST',
    path: '/query',
    handler: function (request, reply) {
        // Qui dovrebbe cercare i dati nel body e rispondere con la query eseguita
        var connection = new Connection(config);

        // Attempt to connect and execute queries if connection goes through
        connection.on('connect', function (err) {
            if (err) {
                console.log(err);
            } else {

                console.log('Connected');
                console.log(request.payload.sql);
                EseguiSql(connection, request.payload.sql, reply);
            }
        });

    }
});

server.connection({
    host: process.env.HOST || 'localhost',
    port: process.env.PORT || 8080
});

var config = {
    userName: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    server: process.env.DB_SERVER,
    options: {
        database: process.env.DB_NAME,
        encrypt: true
    }
}
alberdo dellagiacome
la source
Bienvenue dans StackOverflow. Pourriez-vous donner plus de détails sur votre réponse et comment elle se rapporte à la question posée par OP?
Szymon Maszke