¿Cómo se comparan Express y hapi entre sí?


133

Desde el punto de vista del diseño y desarrollo de aplicaciones web, ¿cómo se comparan Express y Hapi entre sí? Para ejemplos básicos, parecen similares, sin embargo, estoy interesado en aprender más sobre las diferencias clave en la estructura general de la aplicación.

Por ejemplo, por lo que he aprendido, Hapi utiliza un mecanismo de enrutamiento diferente que no tiene en cuenta el orden de registro, puede realizar búsquedas más rápidas, pero es limitado en comparación con Express. ¿Hay otras diferencias importantes?

También hay un artículo sobre cómo elegir Hapi (en lugar de Express) para desarrollar el nuevo sitio web npmjs.com, este artículo establece que "el sistema de complementos de Hapi significa que podemos aislar diferentes facetas y servicios de la aplicación de manera que permitan microservicios en el futuro. Express, por otro lado, requiere un poco más de configuración para obtener la misma funcionalidad ", ¿qué significa exactamente?

Respuestas:


231

Esta es una gran pregunta y requiere una respuesta larga para ser completa, por lo que solo abordaré un subconjunto de las diferencias más importantes. Disculpas porque todavía es una respuesta larga.

¿En qué se parecen?

Tienes toda la razón cuando dices:

Para ejemplos básicos parecen similares

Ambos marcos resuelven el mismo problema básico: proporcionar una API conveniente para construir servidores HTTP en el nodo. Es decir, más conveniente que usar httpsolo el módulo nativo de nivel inferior . El httpmódulo puede hacer todo lo que queramos, pero es tedioso escribir aplicaciones.

Para lograr esto, ambos utilizan conceptos que han existido en marcos web de alto nivel durante mucho tiempo: enrutamiento, controladores, complementos, módulos de autenticación. Puede que no siempre hayan tenido los mismos nombres, pero son más o menos equivalentes.

La mayoría de los ejemplos básicos se parecen a esto:

  • Crea una ruta
  • Ejecute una función cuando se solicite la ruta, preparando la respuesta
  • Responder a la solicitud

Rápido:

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 diferencia no es exactamente innovadora aquí ¿verdad? Entonces, ¿por qué elegir uno sobre el otro?

¿En qué se diferencian?

La respuesta simple es hapi es mucho más y hace mucho más fuera de la caja. Eso podría no estar claro cuando solo mira el ejemplo simple de arriba. De hecho, esto es intencional. Los casos simples se mantienen simples. Así que examinemos algunas de las grandes diferencias:

Filosofía

Express pretende ser muy mínimo. Al darle una pequeña API con solo una fina capa de polvo encima http, todavía está muy solo en términos de agregar funcionalidad adicional. Si desea leer el cuerpo de una solicitud entrante (una tarea bastante común), debe instalar un módulo separado . Si espera que se envíen varios tipos de contenido a esa ruta, también debe verificar el Content-typeencabezado para verificar cuál es y analizarlo en consecuencia (datos de formulario vs JSON vs multiparte, por ejemplo), a menudo utilizando módulos separados .

hapi tiene un rico conjunto de características, a menudo expuesto a través de opciones de configuración, en lugar de requerir que se escriba código. Por ejemplo, si queremos asegurarnos de que el cuerpo de una solicitud (carga útil) se lea completamente en la memoria y se analice de manera adecuada (automáticamente según el tipo de contenido) antes de ejecutar el controlador, es solo una opción simple :

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

        reply(request.payload);
    }
});

Caracteristicas

Solo necesita comparar la documentación de la API en ambos proyectos para ver que hapi ofrece un conjunto de características más grande.

hapi incluye algunas de las siguientes características integradas que Express no incluye (que yo sepa):

Extensibilidad y modularidad.

hapi y Express abordan la extensibilidad de una manera muy diferente. Con Express, tiene funciones de middleware . Las funciones de middleware son como filtros que apila y todas las solicitudes se ejecutan antes de llegar a su controlador.

hapi tiene el ciclo de vida de la solicitud y ofrece puntos de extensión , que son comparables a las funciones de middleware pero existen varios puntos definidos en el ciclo de vida de la solicitud.

Una de las razones por las que Walmart construyó hapi y dejó de usar Express fue la frustración con lo difícil que era dividir una aplicación Express en partes separadas, y hacer que diferentes miembros del equipo trabajen de manera segura en su parte. Por esta razón, crearon el sistema de complementos en hapi.

Un complemento es como una sub-aplicación, puedes hacer todo lo que puedas en una aplicación hapi, agregar rutas, puntos de extensión, etc. En un complemento puedes estar seguro de que no estás rompiendo otra parte de la aplicación, porque el orden de los registros para rutas no importan y no puede crear rutas conflictivas. Luego puede combinar estos complementos en un servidor e implementarlo.

Ecosistema

Debido a que Express le brinda tan poco de forma inmediata, debe mirar hacia afuera cuando necesita agregar algo a su proyecto. Muchas veces cuando trabajas con hapi, la función que necesitas está integrada o hay un módulo creado por el equipo central.

Minimal suena genial. Pero si está creando una aplicación de producción seria, es probable que eventualmente necesite todo esto.

Seguridad

hapi fue diseñado por el equipo de Walmart para ejecutar el tráfico del Black Friday, por lo que la seguridad y la estabilidad siempre han sido una de las principales preocupaciones. Por esta razón, el marco hace muchas cosas adicionales, como limitar el tamaño de la carga útil entrante para evitar agotar la memoria de su proceso. También tiene opciones para cosas como el retraso máximo del bucle de eventos, la memoria RSS máxima utilizada y el tamaño máximo del montón v8, más allá del cual su servidor responderá con un tiempo de espera 503 en lugar de simplemente fallar.

Resumen

Evalúalos a ambos tú mismo. Piense en sus necesidades y cuál de las dos aborda sus mayores preocupaciones. Date un chapuzón en las dos comunidades (IRC, Gitter, Github), mira cuál prefieres. No solo tomes mi palabra. ¡Y feliz pirateo!


DESCARGO DE RESPONSABILIDAD: Soy parcial como autor de un libro sobre hapi y lo anterior es en gran parte mi opinión personal.


77
Matt, gracias por la extensa publicación, las secciones "extensibilidad y modularidad" y "seguridad" fueron las secciones más útiles para mí. Supongo que vale la pena mencionar que el nuevo sistema de enrutamiento en Express 4 proporciona una modularidad mejorada para las sub-aplicaciones.
Ali Shakiba

1
Gran respuesta Matt. También estamos confundidos con Hapi y Express, un inconveniente que estamos viendo con Hapi es que no tiene un soporte comunitario tan extenso como Express y podría ser un problema importante si nos quedamos atrapados en algún lugar. Necesito tu opinión sobre lo mismo.
Aman Gupta

1
Express es genérico, mientras que hapi es un poco más empresarial.
windmaomao

1
@MattHarrison gran respuesta, ahora mismo estoy leyendo tu libro sobre Hapi, es simplemente genial. Estoy a punto de desarrollar un nuevo mercado para libros usando Hapi en backend y vue.js en frontend, después de acostumbrarme a Hapi me gustaría participar activamente en el proyecto Hapi.
Humoyun Ahmad

1
@Humoyun ¡Genial! Sin embargo, tenga en cuenta que hay una nueva versión principal de hapi con algunos cambios considerables desde <= v16.0.0. Actualmente estoy produciendo una serie de screencast diseñada para que la gente aprenda v17: youtube.com/playlist?list=PLi303AVTbxaxqjaSWPg94nccYIfqNoCHz
Matt Harrison el

54

Mi organización se va con Hapi. Por eso nos gusta.

Hapi es:

  • Respaldado por los principales cuerpos. Esto significa que el apoyo de la comunidad será fuerte y estará a su disposición en futuras versiones. Es fácil encontrar personas apasionadas de Hapi, y hay buenos tutoriales por ahí (aunque no tan numerosos y extensos como los tutoriales de ExpressJs). A partir de esta fecha de publicación, npm y Walmart usan Hapi.
  • Puede facilitar el trabajo de equipos distribuidos que trabajan en diversas partes de los servicios de back-end sin tener que tener un conocimiento exhaustivo del resto de la superficie API (la arquitectura de complementos de Hapi es el epítome de esta calidad).
  • Deje que el marco haga lo que se supone que debe hacer un marco: configurar cosas. Después de eso, el marco debería ser invisible y permitir a los desarrolladores centrar su verdadera energía creativa en desarrollar la lógica empresarial. Después de usar Hapi durante un año, definitivamente siento que Hapi logra esto. ¡Me siento feliz!

Si quieres escuchar directamente de Eran Hammer (el líder de Hapi)

En los últimos cuatro años, hapi se convirtió en el marco de elección para muchos proyectos, grandes o pequeños. Lo que hace que hapi sea único es su capacidad de escalar a implementaciones grandes y equipos grandes. A medida que crece un proyecto, también lo hace su complejidad: complejidad de ingeniería y complejidad de procesos. La arquitectura y la filosofía de hapi maneja la mayor complejidad sin la necesidad de refactorizar constantemente el código [leer más]

Comenzar a usar Hapi no será tan fácil como ExpressJs porque Hapi no tiene el mismo "poder estelar" ... pero una vez que te sientas cómodo obtendrás MUCHO kilometraje. Me tomó alrededor de ~ 2 meses como un nuevo hacker que irresponsablemente usó ExpressJs durante algunos años. Si eres un desarrollador experimentado de backend, sabrás cómo leer los documentos, y probablemente ni siquiera te des cuenta.

Áreas en las que la documentación de Hapi puede mejorar:

  1. cómo autenticar usuarios y crear sesiones
  2. manejo de solicitudes de origen cruzado (CORS)
  3. subir archivos (multiparte, fragmentado)

Creo que la autenticación sería la parte más desafiante porque tienes que decidir qué tipo de estrategia de autenticación usar (autenticación básica, cookies, tokens JWT, OAuth). Aunque técnicamente no es un problema de Hapi que el panorama de sesiones / autenticación esté tan fragmentado ... pero desearía que proporcionaran algo de ayuda para esto. Aumentaría enormemente la felicidad del desarrollador.

Los dos restantes no son realmente tan difíciles, los documentos podrían escribirse un poco mejor.


3

Datos breves sobre Hapi o ¿Por qué Hapi JS?

Hapi se centra en la configuración. Tiene autenticación y autorización integradas en el marco. Fue lanzado en una atmósfera probada en batalla y realmente ha demostrado su valía. Todos los módulos tienen una cobertura de prueba del 100%. Registra el nivel más alto de abstracción lejos del núcleo HTTP. Fácilmente comparable a través de la arquitectura del complemento

Hapi es una mejor opción en cuanto al rendimiento Hapi utiliza un mecanismo de enrutamiento diferente, que puede realizar búsquedas más rápidas y tener en cuenta el orden de registro. Sin embargo, es bastante limitado en comparación con Express. Y gracias al sistema de complementos Hapi, es posible aislar las diferentes facetas y servicios que ayudarían a la aplicación de muchas maneras en el futuro.

Uso

Hapi es el marco más preferido en comparación con Express. Hapi se utiliza principalmente para aplicaciones empresariales a gran escala.

Algunas razones por las que los desarrolladores no eligen Express al crear aplicaciones empresariales son:

Las rutas son más difíciles de componer en Express

El middleware se interpone en el camino la mayor parte del tiempo; cada vez que defina las rutas, debe escribir tantos números de códigos.

Hapi sería la mejor opción para un desarrollador que busca construir API RESTful. Hapi tiene una arquitectura de microservicio y también es posible transferir el control de un controlador a otro en función de ciertos parámetros. Con el complemento Hapi, puede disfrutar de un mayor nivel de abstracción en torno a HTTP porque puede dividir la lógica empresarial en partes fácilmente manejables.

Otra gran ventaja de Hapi es que proporciona mensajes de error detallados cuando configura incorrectamente. Hapi también le permite configurar el tamaño de carga de su archivo de forma predeterminada. Si el tamaño máximo de carga es limitado, puede enviar un mensaje de error al usuario indicando que el tamaño del archivo es demasiado grande. Esto protegería su servidor de fallas porque las cargas de archivos ya no intentarán almacenar un archivo completo en el búfer.

  1. Lo que pueda lograr con express también se puede lograr fácilmente con hapi.js.

  2. Hapi.js es muy elegante y organiza muy bien el código. Si ve cómo funciona el enrutamiento y coloca la lógica central en los controladores, definitivamente le encantará.

  3. Hapi.js proporciona oficialmente varios complementos exclusivamente para rangos de hapi.js, desde autenticación basada en token hasta gestión de sesión y muchos más, que es un anuncio. No significa que no se pueda usar el npm tradicional, todos son compatibles con hapi.js

  4. Si codifica en hapi.js, un código sería muy fácil de mantener.


"Si ves cómo hace el enrutamiento y pone la lógica central en los controladores ...". No veo ningún ejemplo en la documentación que muestre el uso de controladores. Todos los ejemplos de enrutamiento utilizan la propiedad del controlador, que es una función. Comparo de esta manera con lo que Laravel (marco PHP) y AdonisJs (marco Node.js) hacen para el enrutamiento en el que podemos usar controladores para el enrutamiento. Probablemente me perdí partes del documento HAPI que muestran el uso de controladores para el enrutamiento. Entonces, si esta característica existe, será bueno para mí, porque estoy acostumbrado a usar controladores para enrutar en Laravel.
Lex Soft

1

Recientemente comencé a usar Hapi y estoy bastante contento con él. Mis razones son

  1. Más fácil de probar. Por ejemplo:

    • server.inject le permite ejecutar la aplicación y obtener una respuesta sin que se ejecute y escuche.
    • server.info da el uri actual, puerto, etc.
    • server.settingsaccede a la configuración, por ejemplo, server.settings.cacheobtiene el proveedor de caché actual
    • En caso de duda, mire las /testcarpetas de cualquier parte de la aplicación o complementos compatibles para ver sugerencias sobre cómo simular / probar / stub, etc.
    • mi sensación es que el modelo arquitectónico de hapi le permite confiar pero verificar, por ejemplo, ¿están registrados mis complementos ? ¿Cómo puedo declarar una dependencia de módulo ?
  2. Funciona fuera de la caja, por ejemplo , cargas de archivos , flujos de retorno desde puntos finales, etc.

  3. Los complementos esenciales se mantienen junto con la biblioteca principal. por ejemplo , análisis de plantillas , almacenamiento en caché , etc. El beneficio adicional es que los mismos estándares de codificación se aplican en todas las cosas esenciales.

  4. Errores sanos y manejo de errores. Hapi valida las opciones de configuración y mantiene una tabla de rutas interna para evitar rutas duplicadas. Esto es bastante útil durante el aprendizaje porque los errores se lanzan temprano en lugar de comportamientos inesperados que requieren depuración.


-1

Solo otro punto para agregar, Hapi ha comenzado a admitir llamadas 'http2' desde la versión 16 en adelante (si no me equivoco). Sin embargo, express aún no admite el módulo 'http2' directamente hasta express 4. Aunque han lanzado la función en la versión alfa de 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
    }
}

Bienvenido a StackOverflow. ¿Podría dar más detalles sobre su respuesta y cómo se relaciona con la pregunta publicada por OP?
Szymon Maszke
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.