In che modo Express e hapi si confrontano?


133

Dal punto di vista della progettazione e dello sviluppo di applicazioni Web, come si confrontano Express e Hapi? Per esempi di base sembrano simili, tuttavia sono interessato a saperne di più sulle differenze chiave nella struttura generale dell'applicazione.

Ad esempio, per quanto ho appreso, Hapi utilizza un diverso meccanismo di routing che non tiene conto dell'ordine di registrazione, può effettuare ricerche più veloci, ma è limitato rispetto a Express. Ci sono altre differenze importanti?

C'è anche un articolo sulla scelta di Hapi (su Express) per lo sviluppo del nuovo sito Web npmjs.com, questo articolo afferma che "Il sistema di plug-in di Hapi significa che possiamo isolare diverse sfaccettature e servizi dell'applicazione in modi che consentirebbero microservizi nel futuro. Express, invece, richiede un po 'più di configurazione per ottenere la stessa funzionalità ", cosa significa esattamente?

Risposte:


231

Questa è una grande domanda e richiede una lunga risposta per essere completa, quindi affronterò solo un sottoinsieme delle differenze più importanti. Chiedo scusa che sia ancora una risposta lunga.

Come sono simili?

Hai perfettamente ragione quando dici:

Per esempi di base sembrano simili

Entrambi i framework stanno risolvendo lo stesso problema di base: fornire un'API conveniente per la creazione di server HTTP nel nodo. Vale a dire, più conveniente rispetto all'utilizzo del httpsolo modulo nativo di livello inferiore . Il httpmodulo può fare tutto ciò che vogliamo, ma è noioso scrivere applicazioni.

Per raggiungere questo obiettivo, entrambi utilizzano concetti che sono presenti da tempo in framework Web di alto livello: routing, gestori, plug-in, moduli di autenticazione. Potrebbero non avere sempre avuto gli stessi nomi ma sono approssimativamente equivalenti.

La maggior parte degli esempi di base è simile a questa:

  • Crea un percorso
  • Esegui una funzione quando viene richiesto il percorso, preparando la risposta
  • Rispondi alla richiesta

Esprimere:

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 differenza non è esattamente rivoluzionaria qui, giusto? Quindi perché scegliere uno sopra l'altro?

Come sono differenti?

La semplice risposta è che hapi è molto di più e fa molto di più immediatamente. Ciò potrebbe non essere chiaro se guardi il semplice esempio dall'alto. In realtà, questo è intenzionale. I casi semplici sono mantenuti semplici. Esaminiamo quindi alcune delle grandi differenze:

Filosofia

Express è pensato per essere molto minimale. Dandoti una piccola API con solo una leggera spolverata in cima http, sei ancora molto da solo in termini di aggiunta di funzionalità aggiuntive. Se vuoi leggere il corpo di una richiesta in arrivo (un'attività abbastanza comune), devi installare un modulo separato . Se stai aspettando che vari tipi di contenuto vengano inviati a quella route, devi anche controllare l' Content-typeintestazione per verificare quale sia e analizzarla di conseguenza (form-data vs JSON vs multi-part per esempio), spesso usando moduli separati .

hapi ha un ricco set di funzionalità, spesso esposto attraverso opzioni di configurazione, piuttosto che richiedere la scrittura di codice. Ad esempio, se vogliamo assicurarci che un corpo di richiesta (payload) sia completamente letto in memoria e adeguatamente analizzato (basato automaticamente sul tipo di contenuto) prima dell'esecuzione del gestore, è solo una semplice opzione :

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

        reply(request.payload);
    }
});

Caratteristiche

Devi solo confrontare la documentazione API su entrambi i progetti per vedere che hapi offre un set di funzionalità più ampio.

hapi include alcune delle seguenti funzionalità integrate che Express non (per quanto ne so):

Estensibilità e modularità

hapi ed Express affrontano l'estensibilità in un modo completamente diverso. Con Express hai le funzioni del middleware . Le funzioni del middleware sono un po 'come i filtri che accumuli e tutte le richieste vengono eseguite prima di colpire il gestore.

hapi ha il ciclo di vita della richiesta e offre punti di estensione , che sono paragonabili alle funzioni del middleware ma esistono diversi punti definiti nel ciclo di vita della richiesta.

Uno dei motivi per cui Walmart ha creato hapi e ha smesso di usare Express è stata una frustrazione per quanto fosse difficile dividere un'app Express in parti separate e far sì che diversi membri del team lavorassero in sicurezza sul loro pezzo. Per questo motivo hanno creato il sistema di plugin in hapi.

Un plug-in è come un'applicazione secondaria, puoi fare tutto il possibile in un'app hapi, aggiungere percorsi, punti di estensione ecc. In un plug-in puoi essere sicuro di non interrompere un'altra parte dell'applicazione, perché l'ordine di le registrazioni per le rotte non contano e non è possibile creare rotte in conflitto. È quindi possibile combinare questi plugin in un server e distribuirlo.

Ecosistema

Poiché Express ti offre così poco fuori dagli schemi, devi guardare fuori quando devi aggiungere qualcosa al tuo progetto. Molte volte quando si lavora con hapi, la funzione di cui hai bisogno è integrata o c'è un modulo creato dal team principale.

Il minimo suona alla grande. Ma se stai costruendo un'app di produzione seria, è probabile che alla fine avrai bisogno di tutte queste cose.

Sicurezza

hapi è stato progettato dal team di Walmart per gestire il traffico del Black Friday, quindi la sicurezza e la stabilità sono sempre state al primo posto. Per questo motivo il framework fa molte cose in più come limitare le dimensioni del payload in entrata per evitare di esaurire la memoria del processo. Ha anche opzioni per cose come il ritardo massimo del loop di eventi, la memoria RSS massima utilizzata e la dimensione massima dell'heap v8, oltre la quale il tuo server risponderà con un timeout 503 anziché semplicemente arrestarsi in modo anomalo.

Sommario

Valutali entrambi da soli. Pensa ai tuoi bisogni e quale dei due risponde alle tue maggiori preoccupazioni. Fai un tuffo nelle due comunità (IRC, Gitter, Github), vedi quale preferisci. Non limitarti a prendere la mia parola. E buon hacking!


DISCLAIMER: Sono di parte come autore di un libro su hapi e quanto sopra è in gran parte la mia opinione personale.


7
Matt, grazie per l'ampio post, le sezioni "estensibilità e modularità" e "sicurezza" sono state le sezioni più utili per me. Immagino che valga la pena ricordare che il nuovo sistema di routing in Express 4 offre una modularità migliorata per le applicazioni secondarie.
Ali Shakiba,

1
Ottima risposta Matt. Siamo anche confusi tra Hapi ed Express, uno svantaggio che stiamo vedendo con Hapi è che non ha un supporto comunitario esteso come Express e potrebbe essere un grosso problema se restiamo bloccati da qualche parte. Hai bisogno della tua opinione riguardo lo stesso.
Aman Gupta,

1
Express è generico, mentre hapi è un po 'più aziendale.
windmaomao,

1
@MattHarrison ottima risposta, in questo momento sto leggendo il tuo libro su Hapi, è semplicemente fantastico. Sto per sviluppare un nuovo mercato per i libri usando Hapi su backend e vue.js su frontend, dopo essermi abituato a Hapi vorrei partecipare attivamente al progetto Hapi.
Humoyun Ahmad il

1
@Humoyun Great! Ricorda però che esiste una nuova versione principale di hapi con alcune modifiche considerevoli da <= v16.0.0. Attualmente sto producendo una serie screencast progettata per far apprendere alle persone v17: youtube.com/playlist?list=PLi303AVTbxaxqjaSWPg94nccYIfqNoCHz
Matt Harrison,

54

La mia organizzazione sta andando con Hapi. Questo è il motivo per cui ci piace.

Hapi è:

  • Supportato da importanti corpi. Ciò significa che il supporto della community sarà forte e disponibile per le versioni future. È facile trovare persone Hapi appassionate e ci sono buoni tutorial là fuori (anche se non così numerosi e tentacolari come i tutorial di ExpressJ). A partire da questa data post npm e Walmart usano Hapi.
  • Può facilitare il lavoro dei team distribuiti che lavorano su varie parti dei servizi di back-end senza dover avere una conoscenza completa del resto della superficie dell'API (l'architettura dei plug-in di Hapi è l'epitome di questa qualità).
  • Lascia che il framework faccia quello che un framework dovrebbe: configurare le cose. Dopodiché il framework dovrebbe essere invisibile e consentire agli sviluppatori di concentrare la loro vera energia creativa sulla costruzione della logica aziendale. Dopo aver usato Hapi per un anno, sento sicuramente che Hapi ci riesce. Mi sento felice!

Se vuoi sentire direttamente da Eran Hammer (il protagonista di Hapi)

Negli ultimi quattro anni l'api è cresciuta fino a diventare il quadro di scelta per molti progetti, grandi o piccoli. Ciò che rende unica hapi è la sua capacità di adattarsi a grandi schieramenti e team di grandi dimensioni. Man mano che un progetto cresce, aumenta anche la sua complessità - complessità ingegneristica e complessità del processo. l'architettura e la filosofia di hapi gestiscono la maggiore complessità senza la necessità di riformattare costantemente il codice [leggi di più]

Iniziare con Hapi non sarà facile come ExpressJs perché Hapi non ha lo stesso "potere stellare" ... ma una volta che ti senti a tuo agio otterrai MOLTO chilometraggio. Mi ci sono voluti circa 2 mesi come nuovo hacker che ha usato irresponsabilmente gli ExpressJ per alcuni anni. Se sei uno sviluppatore di backend esperto saprai leggere i documenti e probabilmente non lo noterai nemmeno.

Aree su cui la documentazione di Hapi può migliorare:

  1. come autenticare gli utenti e creare sessioni
  2. gestione delle richieste di origine incrociata (CORS)
  3. caricamento di file (multipart, grosso)

Penso che l'autenticazione sarebbe la parte più impegnativa perché devi decidere quale tipo di strategia di autenticazione usare (autenticazione di base, cookie, token JWT, OAuth). Anche se tecnicamente non è un problema di Hapi che il panorama delle sessioni / autenticazione sia così frammentato ... ma vorrei che fornissero un po 'di mano per questo. Aumenterebbe notevolmente la felicità degli sviluppatori.

I restanti due non sono poi così difficili, i documenti potrebbero essere scritti leggermente meglio.


3

Fatti rapidi su Hapi o perché Hapi JS?

Hapi è incentrato sulla configurazione Ha un'autenticazione e un'autorizzazione integrate nel framework È stato rilasciato in un'atmosfera testata in battaglia e ha davvero dimostrato il suo valore Tutti i moduli hanno una copertura del test al 100% Registra il massimo livello di astrazione dal core HTTP Facilmente compattabile tramite l'architettura del plugin

Hapi è una scelta migliore in termini di prestazioni Hapi utilizza un diverso meccanismo di routing, che può effettuare ricerche più rapide e tenere conto dell'ordine di registrazione. Tuttavia, è piuttosto limitato rispetto a Express. E grazie al sistema di plugin Hapi, è possibile isolare le diverse sfaccettature e servizi che potrebbero aiutare l'applicazione in molti modi in futuro.

uso

Hapi è il framework più preferito rispetto a Express. Hapi è utilizzato principalmente per applicazioni aziendali su larga scala.

Un paio di motivi per cui gli sviluppatori non scelgono Express durante la creazione di applicazioni aziendali sono:

Le rotte sono più difficili da comporre in Express

Il middleware si mette in mezzo per la maggior parte del tempo; ogni volta che si definiscono i percorsi, è necessario scrivere quanti più numeri di codici.

Hapi sarebbe la scelta migliore per uno sviluppatore che desidera creare API RESTful. Hapi ha un'architettura micro-service ed è anche possibile trasferire il controllo da un gestore all'altro in base a determinati parametri. Con il plugin Hapi, puoi godere di un maggiore livello di astrazione attorno a HTTP perché puoi dividere la logica di business in pezzi facilmente gestibili.

Un altro enorme vantaggio di Hapi è che fornisce messaggi di errore dettagliati in caso di configurazione errata. Hapi ti consente anche di configurare la dimensione del caricamento del file per impostazione predefinita. Se la dimensione massima di caricamento è limitata, è possibile inviare un messaggio di errore all'utente indicando che la dimensione del file è troppo grande. Ciò proteggerebbe il tuo server dagli arresti anomali perché i caricamenti dei file non tenteranno più di bufferizzare un intero file.

  1. Qualunque cosa tu possa ottenere usando express può anche essere facilmente raggiunta usando hapi.js.

  2. Hapi.js è molto elegante e organizza molto bene il codice. Se vedi come funziona il routing e inserisce la logica principale nei controller, lo adorerai provocatoriamente.

  3. Hapi.js fornisce ufficialmente diversi plug-in esclusivamente per gli intervalli hapi.js dall'autorizzazione basata su token alla gestione della sessione e molti altri, che è un annuncio attivo. Ciò non significa che l'npm tradizionale non possa essere utilizzato, tutti sono supportati da hapi.js

  4. Se si codifica in hapi.js, un codice sarebbe molto gestibile.


"Se vedi come funziona il routing e inserisce la logica principale nei controller ...". Non vedo alcun esempio nella documentazione che mostra l'uso dei controller. Tutti gli esempi di routing utilizzano la proprietà del gestore che è una funzione. Confronto in questo modo con ciò che fanno Laravel (framework PHP) e AdonisJs (framework Node.js) per il routing in cui possiamo usare i controller per il routing. Probabilmente ho perso parti del documento HAPI che mostrano l'uso dei controller per il routing. Quindi, se questa funzione esiste, sarà bene per me, perché sono abituato a usare i controller per il routing in Laravel.
Lex Soft

1

Ho iniziato a usare Hapi di recente e ne sono abbastanza contento. Le mie ragioni sono

  1. Più facile da testare. Per esempio:

    • server.inject ti consente di eseguire l'app e ottenere una risposta senza che sia in esecuzione e in ascolto.
    • server.info fornisce l'uri, la porta ecc. correnti
    • server.settingsaccede alla configurazione, ad esempio server.settings.cacheottiene l'attuale provider di cache
    • in caso di dubbi, guarda le /testcartelle per qualsiasi parte dell'app o plugin supportati per vedere suggerimenti su come deridere / testare / stub ecc.
    • ho la sensazione che il modello architettonico di hapi ti permetta di fidarti, ma verifica ad es. I miei plugin sono registrati ? Come posso dichiarare a dipendenza del modulo ?
  2. Funziona fuori dalla scatola, ad es immediatamente , Upload di file , flussi di ritorno da endpoint ecc.

  3. I plug-in essenziali vengono mantenuti insieme alla libreria principale. ad es. analisi del modello , memorizzazione nella cache , ecc. Il vantaggio aggiunto è che gli stessi standard di codifica vengono applicati a tutti gli elementi essenziali.

  4. Errori sani e gestione degli errori. Hapi convalida le opzioni di configurazione e mantiene una tabella di route interna per impedire route duplicate. Questo è abbastanza utile durante l'apprendimento perché gli errori vengono generati in anticipo anziché comportamenti inaspettati che richiedono il debug.


-1

Solo un altro punto da aggiungere, Hapi ha iniziato a supportare le chiamate "http2" dalla versione 16 in poi (se non sbaglio). Tuttavia, express deve ancora supportare il modulo 'http2' direttamente fino a express 4. Sebbene abbiano rilasciato la funzione nella versione alfa di express 5.


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

Benvenuto in StackOverflow. Potresti approfondire la tua risposta e come si collega alla domanda posta da OP?
Szymon Maszke,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.