tutorial node example ejemplos node.js express frameworks hapijs

node.js - node - ¿Cómo se comparan Express y hapi entre sí?



node js express example (7)

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, hasta donde sé, Hapi utiliza un mecanismo de enrutamiento different 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 article 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?


Esta es una gran pregunta y requiere una respuesta larga para ser completa, así 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 solo el módulo http nativo de nivel inferior. El módulo http puede hacer todo lo que queramos, pero es tedioso escribir aplicaciones.

Para lograr esto, ambos usan 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

Exprimir:

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 sobre 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 encabezado de Content-type 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 el 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 adecuadamente (automáticamente según el tipo de contenido) antes de ejecutar el controlador, es solo una option 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, puede hacer todo lo que pueda en una aplicación hapi, agregar rutas, puntos de extensión, etc. En un complemento puede estar seguro de que no está 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 manera 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 preocupación principal. 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 los 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.


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 los 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 con 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 usó irresponsablemente ExpressJs durante unos 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 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.


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 permite ejecutar la aplicación y obtener una respuesta sin que se ejecute y escuche.
    • server.info proporciona la uri actual, el puerto, etc.
    • server.settings accede a la configuración, por ejemplo, server.settings.cache obtiene el proveedor de caché actual
    • cuando tenga dudas, mire las carpetas /test 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 de forma inmediata , por ejemplo , cargas de archivos , devoluciones de secuencias desde puntos finales, etc.

  3. Los complementos esenciales se mantienen junto con la biblioteca principal. por ejemplo, análisis de plantillas , caching , 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 mientras se aprende porque los errores se lanzan temprano en lugar de comportamientos inesperados que requieren depuración.


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 todavía debe admitir el módulo ''http2'' directamente hasta express 4. Aunque han lanzado la función en la versión alfa de express 5.


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 cuando crean 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 administració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.


const Hapi = require(''hapi''); var Connection = require(''tedious'').Connection; var Request = require(''tedious'').Request; var TYPES = require(''tedious'').TYPES; const server = new Hapi.Server(); var vorpal = require(''vorpal'')(); server.connection({ host: process.env.HOST || ''localhost'', port: process.env.PORT || 3000 }); server.start(function (err) { if (err) { throw err; } console.log("server running at : " + server.info.uri); }); var config = { userName: ''sa'', password: ''password.123'', server: ''localhost'', options: { database: '''', port: 1433 } } server.route( { method: ''GET'', path: ''/{categoria}'', handler: function (request, reply) { var connection = new Connection(config); connection.on(''connect'', function (err) { if (err) { console.log(err); } else { console.log(''Connected''); EseguiSqlGet(connection, request.params.categoria, reply); } }); } } ); function EseguiSqlGet(connection, cat, reply) { var rows = []; var sql = ''SELECT * FROM Prodotti INNER JOIN Categorie on Categorie.IdCategoria = Prodotti.IdCategoria WHERE Categorie.IdCategoria = '' + cat ; request_sql = new Request(sql, function(err, rowCount) { if (err) { console.log(err); } else { console.log(rowCount + '' rows''); console.log("Invio Reply") reply(rows); } }); request_sql.on(''row'', function(columns) { var row = {}; columns.forEach(function (column) { row[column.metadata.colName] = column.value; }); rows.push(row); }); connection.execSql(request_sql); } // POST server.route( { method: ''POST'', path: ''/inserisci'', handler: function (request, reply) { var connection = new Connection(config); connection.on(''connect'', function (err) { if (err) { console.log(err); } else { console.log(''Connected''); EseguiSqlPost(connection,reply, request.payload.idcat, request.payload.nome, request.payload.prezzo ); } }); } } ); function EseguiSqlPost(connection,reply, cat,nome,prezzo) { var sql = "INSERT INTO Prodotti VALUES("+ cat +",''"+nome+"'',"+prezzo+")"; request_sql = new Request(sql, function(err, rowCount) { if (err) { console.log(err); } else { console.log(rowCount + '' rows''); console.log("Invio Reply") reply(''riga aggiunta''); } }); /*request_sql.on(''row'', function(columns) { var row = {}; columns.forEach(function (column) { row[column.metadata.colName] = column.value; }); rows.push(row); }); */ connection.execSql(request_sql); } //VORPAL COMMAND PROMT var categoria = [ { ''idcategoria'':''1'', ''nome'':''ciao'', } ] vorpal .command(''inserisci <categoria> <nome>'') .action(function(args, callback) { categoria.push( {''idcategoria'':args.categoria,''nome'':args.nome} ); console.log(JSON.stringify(categoria)); callback(); }); vorpal .delimiter("delimeter") .show();


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