Hapi vs Express: comparación de marcos web de Node.js

H

Es probable que ya hayas oído hablar de Hapi. Y es posible que se pregunte cómo se compara con el marco web Express en el desarrollo de Node.js. En este artículo, compararemos los frameworks cara a cara y exploraremos las diferencias en la experiencia del desarrollador.

Similitudes y diferencias entre Hapi y Express

Ambos Express y Hapi aspiran a ser altamente flexibles, simples y extensibles. Esta similitud significa que ambos tienen API fáciles de usar, son altamente modulares y pueden admitir su aplicación a medida que crece potencialmente mucho.

La curva de aprendizaje de estos marcos, dado que son bastante sencillos, es baja, a diferencia de un marco más obstinado como Meteor. Si viene de usar Express, debería poder recoger rápidamente a Hapi, y viceversa.

Sin embargo, existen algunas diferencias filosóficas entre los dos marcos, que describiremos a lo largo de este artículo.

Hapi.js incluye más “baterías” por defecto que Express. Por ejemplo, al analizar la carga útil de formularios a través de solicitudes “POST”, con Express, debe incluir el body-parser middleware. Luego lo usa para analizar la carga útil POST y usar los datos del usuario. Por otro lado, con Hapi, no necesita middleware. La carga útil es analizada por usted por el propio marco, y puede acceder a la carga útil directamente en el objeto de solicitud. Pequeñas comodidades como esa abundan en Hapi.

Enrutamiento y servidor básico

Para empezar crearemos hapiapp, una aplicación de ejemplo de Hapi que muestra la funcionalidad básica de Hapi. También crearemos expressapp, una imagen especular de hapiapp que usa Express para que podamos comparar los marcos uno al lado del otro.

Nuestras dos aplicaciones utilizarán ES6 JavaScript.

Cree dos directorios, hapiapp y expressapp.

Dentro de cada uno, ejecute el comando:

$ npm init

Luego acepte todos los valores predeterminados presionando “Enter”. Esto crea un archivo package.json dentro de cada directorio, creando así nuestras dos aplicaciones diferentes, hapiapp y expressapp.

Primero, veamos el enrutamiento básico en acción en Hapi.js. Dentro del directorio hapiapp, instale el módulo Hapi ejecutando el siguiente comando:

$ npm install [email protected] --save

Luego crea un archivo index.js con el siguiente contenido:

// hapiapp/index.js

const Hapi = require('hapi');

// create our server
const server = new Hapi.Server();

// configure the port
server.connection({
    port: 8000,
    host: 'localhost'
});

// basic routes
server.route({
    method: 'GET',
    path: "https://Pharos.sh.com/",
    handler: (request, reply) => reply('Hello World')
});

server.route({
    method: 'GET',
    path: '/hello/{name}',
    handler: (request, reply) => reply(`Hello ${request.params.name}`)
});

// start the server
server.start((err) => {
    if (err) {
        console.error(err);
    }

    console.log('App running on port 8000...');
});

Esta aplicación básica crea dos rutas en localhost:8000 y localhost:8000/hello/<name>. El primero imprimirá un simple “Hola mundo” para el usuario. La segunda, sin embargo, es una ruta dinámica que imprime “Hola” seguido del nombre que ingresamos en el navegador después de la segunda barra “/”.

Desde el directorio hapiapp, ejecute la aplicación usando el siguiente comando y pruebe las URL dadas anteriormente:

$ node index.js

La aplicación Express equivalente implica una configuración muy similar. Dentro de nuestro directorio expressapp, instale Express como una dependencia usando el comando:

$ npm install [email protected] --save

Luego crea un archivo index.js con el siguiente contenido:

// expressapp/index.js

const express = require('express');

// create our app
const app = express();

// basic routes
app.get("https://Pharos.sh.com/", (req, res) => res.send('Hello World!'));
app.get('/hello/:name', (req, res) => res.send(`Hello ${req.params.name}`));

// start the server
app.listen(3000, () => console.log('App running on port 3000...'));

Esto logra la misma funcionalidad que la aplicación Hapi. Para ejecutarlo, desde dentro de nuestro directorio expressapp, ejecute el comando:

$ node index.js

Visite las rutas indicadas anteriormente, excepto que ahora estamos corriendo en localhost:3000 en vez de localhost:8000.

Ambos ejemplos de código tienen una simple simplicidad. La configuración del servidor es notablemente similar, aunque Express parece más compacto aquí.

Trabajando con Middleware

“Middleware” es el nombre que se le da a los módulos de software que funcionan en solicitudes HTTP en sucesión antes de que el resultado final se devuelva al usuario en una respuesta.

El middleware puede funcionar como funciones que definimos dentro de la aplicación, o funciones definidas en bibliotecas de middleware de terceros.

Express le permite adjuntar middleware para manejar cada solicitud. Sin embargo, Hapi trabaja con plugins que brindan funcionalidad de middleware.

Un ejemplo de middleware son los tokens CSRF, que ayudan a prevenir ataques de piratería de la falsificación de solicitudes entre sitios (CSRF). Sin tokens CSRF, los atacantes pueden hacerse pasar por solicitudes legítimas y robar datos o ejecutar código malicioso en sus aplicaciones.

Los enfoques adoptados por Hapi y Express para hacer frente a los ataques CSRF son similares. Implica generar un token secreto en el servidor para cada formulario que envía datos al servidor. Luego, el servidor verifica cada solicitud POST para el token correcto y difícil de falsificar. Si está ausente, el servidor rechaza la solicitud, evitando así intentos de ejecución de código malicioso. En este caso, el middleware generará un token CSRF para cada solicitud para que no tenga que hacerlo dentro del código de su aplicación.

La diferencia entre los dos marcos aquí es principalmente cosmética, con Hapi usando un plugin, Crumb, para la generación y procesamiento de tokens. Express, por otro lado, utiliza un middleware conocido como csurf para generar y procesar tokens CSRF.

Aquí hay un ejemplo de código Hapi que genera tokens Crumb:

'use strict';

const Hapi = require('hapi');
const Vision = require('vision');

const server = new Hapi.Server({
    host: '127.0.0.1',
    port: 8000
});

const plugins = [
    Vision,
    {
        plugin: require('../'),
        options: {
            restful: true
        }
    }
];

// Add Crumb plugin
(async () => {
    await server.register(plugins);

    server.route([
        // a "crumb" cookie should be set with any request
        // for cross-origin requests, set CORS "credentials" to true
        // a route returning the crumb can be created like this

        {
            method: 'GET',
            path: '/generate',
            handler: function (request, h) {

                return {
                    crumb: server.plugins.crumb.generate(request, h)
                };
            }
        },

        // request header "X-CSRF-Token" with crumb value must be set in request for this route

        {
            method: 'PUT',
            path: '/crumbed',
            handler: function (request, h) {

                return 'Crumb route';
            }
        }
    ]);

    await server.start();

    console.log('Example restful server running at:', server.info.uri);
})();

Y el código Express aproximadamente equivalente usando csurf.

const cookieParser = require('cookie-parser')
const csrf = require('csurf')
const bodyParser = require('body-parser')
const express = require('express')

// setup route middlewares
let csrfProtection = csrf({ cookie: true })
let parseForm = bodyParser.urlencoded({ extended: false })

// create express app
let app = express()

// parse cookies
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser())

app.get('/form', csrfProtection, function (req, res) {
    // pass the csrfToken to the view
    res.render('send', { csrfToken: req.csrfToken() })
});

app.post('/process', parseForm, csrfProtection, function (req, res) {
    res.send('data is being processed')
});

Las bibliotecas de middleware también pueden ayudarlo con la implementación de otras funciones comunes, como la autenticación. Puede encontrar una lista de algunos de los middleware Express en este directorio de middleware. Para Hapi, hay un biblioteca de plugins de Hapi.

Entrega de imágenes y activos estáticos

El servicio de archivos estáticos es muy similar entre Hapi y Express. Hapi usa el Inerte plugin, mientras que Express utiliza el express.static middleware incorporado.

En nuestra aplicación hapiapp, podemos servir archivos estáticos desde un directorio público creando un directorio en hapiapp/public y la colocación en él de un archivo runsagainstbulls.jpg.

Aquí está el archivo de imagen. Haga clic derecho y descárguelo en su aplicación.

También querrás instalar [email protected] utilizando:

$ npm install [email protected] --save

Ahora podemos servir el archivo estático usando el siguiente código. Agregue esto antes de las líneas que inician el servidor en hapiapp/index.js.

// hapiapp/index.js

...

// serve static files using inert
// require inert for this route
server.register(require('inert'), (err) => {
    if (err) {
        throw err;
    }

    // serve the runswithbulls image from the public folder
    server.route({
        method: 'GET',
        path: '/image',
        handler: (request, reply) => {
            reply.file('./public/runsagainstbulls.jpg');
        }
    });
});

Ejecute el servidor nuevamente y visite localhost:8000/image y debería ver la imagen que se le devuelve.

En Express, hay un poco menos de configuración. Simplemente copie la misma imagen en una carpeta pública recién creada dentro de expressapp. Luego agregue esta línea a su archivo index.js antes de la línea que inicia el servidor:

// expressapp/index.js

...

// serve static files from the public folder
app.use(express.static('public'));

Inicie su servidor y visite localhost:3000/runsagainstbulls.jpg y debería ver la imagen que ofrece Express.

De forma predeterminada, Express servirá todos los archivos en la carpeta configurada y Hapi se puede configurar para que haga lo mismo con Inert, aunque no es la configuración predeterminada.

Usar motores de plantilla

Tanto Hapi como Express pueden renderizar y servir plantillas como usar motores como Handlebars, Twig, EJS y otros.

En nuestra carpeta hapiapp, instale el plugin de visión para brindar soporte con:

$ npm install [email protected] --save

También instalar Bigote daliniano motor de plantillas con:

$ npm install [email protected] --save

Luego, creemos una plantilla de Handlebars en un nuevo directorio de plantillas y un nuevo archivo en ese directorio, llamado cats.html. Vamos a hacer una lista de gatos y mostrarlos usando esta plantilla.

Actualice cats.html de la siguiente manera:

<!-- hapiapp/templates/cats.html -->
<h2>All My Cats</h2>

<ol>
    {{#each cats}}
        <li>{{name}}</li>
    {{/each}}
</ol>

Esta plantilla de Handlebars recorre una colección de objetos de gatos y muestra el nombre de cada gato.

Dentro de index.js, antes de iniciar el servidor, agregue el siguiente código, que configura la visión y crea una ruta en /catsy proporciona una lista de gatos que se mostrarán allí.

// hapiapp/index.js

...

// configure vision to render Handlebars templates
server.register(require('vision'), (err) => {
    if (err) {
        throw err;
    }

    server.views({
        engines: {
            html: require('handlebars'),
        },
        path: __dirname + '/templates'
    });
});

// display cats at /cats using the cats handlebars template
server.route({
    method: 'GET',
    path: '/cats',
    handler: (request, reply) => {

        reply.view('cats', {
            cats: [
                {name: "Blinky the Cat"},
                {name: "Sammy the Happy Cat"},
                {name: "Eto the Thug Cat"},
                {name: "Liz a quiet cat"},
            ]
        });
    }
});

Inicie el servidor nuevamente y visite localhost:8000/cats, y debería ver una lista de gatos en la página.

Para replicar esta funcionalidad en Express, primero cree un directorio llamado vistas dentro del directorio raíz de expressapp. Luego crea un archivo de Handlebars cats.hbs con contenido similar al que teníamos para Hapi.

<!-- expressapp/views/cats.hbs -->
<h2>All My Cats</h2>

<ol>
    {{#each cats}}
        <li>{{name}}</li>
    {{/each}}
</ol>

Ahora, de vuelta en expressapp, instale el motor Handlebars para Express con:

$ npm install [email protected] --save

Luego, para renderizar la plantilla y mostrar nuestros gatos, actualice index.js, agregando el siguiente código justo antes de la parte que inicia el servidor:

// expressapp/index.js

...

// use handlebars as the view engine
app.set('view engine', 'hbs');

// display a list of cats at /cats
app.get('/cats', function (req, res) {

    res.render('cats', {
        cats: [
            {name: "Blinky the Cat"},
            {name: "Sammy the Happy Cat"},
            {name: "Eto the Thug Cat"},
            {name: "Liz a quiet cat"},
        ]
    });
});

Reinicie el servidor Express y visite localhost:3000/cats, y deberíamos ver la lista de gatos mostrada como estaba para Hapi.

Notará que el ejemplo de Hapi necesitaba un poco más de configuración que el ejemplo Express. Sin embargo, ambos ejemplos de código son bastante sencillos de seguir. En una aplicación pequeña como esta, hay poco para elegir entre los dos marcos. En una aplicación más grande, sin embargo, habrá diferencias más notables. Por ejemplo, Hapi tiene la reputación de incluir más código repetitivo que Express. Por otro lado, también hace un poco más por usted que Express, por lo que definitivamente hay compensaciones.

Conectarse a una base de datos

MongoDB es una base de datos NoSQL probada en batalla para usar en sus aplicaciones. Con el mangosta biblioteca de modelado de objetos, podemos conectar MongoDB a las aplicaciones Hapi y Express. Para esta parte, querrá tener MongoDB instalado y ejecutándose en su sistema si aún no lo tiene.

Para ver cómo funciona esto para ambos marcos, actualicemos nuestras aplicaciones de muestra para almacenar objetos cat en una base de datos. Asimismo, nuestros listados buscarán objetos cat de la base de datos.

Los pasos que tenemos que seguir son:

  • Configurar una conexión de MongoDB a localhost
  • Definir un esquema de mangosta
  • Crea objetos
  • Almacenar objetos
  • Recuperar documentos de la base de datos

De vuelta en nuestro directorio hapiapp, instale mangosta con:

$ npm install [email protected] --save

Dado que queremos la capacidad de crear nuevos gatos a partir de nuestro /cats ver, actualice la plantilla de gatos para incluir un formulario para gatos nuevos. Agregue las siguientes líneas encima de la lista de gatos en hapiapp/templates/cats.html.

<!-- hapiapp/templates/cats.html -->

...

<form class="" action="/cats" method="post">
    <input type="text" name="name" placeholder="New cat">
</form>

Ahora podemos importar mangosta en la parte superior de nuestro archivo index.js. Justo debajo de la línea donde importamos Hapi, agregue las siguientes líneas:

// hapiapp/index.js

...

const Hapi = require('hapi');
const mongoose = require('mongoose');

// connect to MongoDB
mongoose.connect('mongodb://localhost/hapiappdb', { useMongoClient: true })
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.error(err));

// create a Cat model that can be persisted to the database
const Cat = mongoose.model('Cat', {name: String});

Ahora actualice nuestra ruta “GET” para /cats para buscar gatos dinámicamente desde MongoDB. También agregaremos una nueva ruta “POST” donde podemos guardar gatos en la base de datos. Aquí está el código para realizar estos cambios, reemplazando el antiguo GET /cats código:

// hapiapp/index.js

...

// display cats at /cats using the cats handlebars template
server.route({
    method: 'GET',
    path: '/cats',
    handler: (request, reply) => {

        let cats = Cat.find((err, cats) => {
            console.log(cats);
            reply.view('cats', {cats: cats});
        });
    }
});

// post cats
server.route({
    method: 'POST',
    path: '/cats',
    handler: (request, reply) => {
        let catname = request.payload.name;
        let newCat = new Cat({name: catname});

        newCat.save((err, cat) => {
            if (err) {
                console.error(err);
            }

            return reply.redirect().location('cats');
        });
    }
});

Ahora, cuando vuelva a ejecutar el servidor y visite localhost:8000/cats, verá que la lista de gatos ahora está vacía. Pero hay un formulario donde puede ingresar nuevos nombres de gatos y presionar Entrar key, y sus gatos se guardarán en MongoDB. Ingrese algunos gatos y obtendrá una lista de gatos, como se muestra a continuación:

Para Express, la instalación y configuración de Mongoose se verá casi igual. De vuelta en expressapp, instale Mongoose con:

$ npm install [email protected] --save

Luego agregue el código de conexión de Mongoose y Cat modelo en index.js después de importar Express.

// expressapp/index.js

...

const express = require('express');
const mongoose = require('mongoose');

// connect to MongoDB
mongoose.connect('mongodb://localhost/expressappdb', { useMongoClient: true })
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.error(err));

// create a Cat model that can be persisted to the database
const Cat = mongoose.model('Cat', {name: String});

Note la similitud con lo que hicimos por Hapi.

Ahora actualice nuestra plantilla expressapp / views / cats.hbs con el código del formulario.

<!-- expressapp/views/cats.hbs -->

...

<form class="" action="/cats" method="post">
    <input type="text" name="name" placeholder="New cat">
</form>

Para permitir que Express analice el contenido del formulario, necesitaremos importar el middleware body-parser. Después de la línea de importación Express en index.js, importe body-parser de la siguiente manera:

// expressapp/index.js

...

const express = require('express');
const bodyParser = require('body-parser')

Además, active el middleware después de la línea creando nuestra aplicación Express, de la siguiente manera:

// expressapp/index.js

...

// create our app
const app = express();

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));

Ahora actualice nuestra ruta “GET” para /cats y cree una nueva ruta “POST” que cree gatos en la base de datos:

// expressapp/index.js

...

// display a list of cats at /cats
app.get('/cats', function (req, res) {

    let cats = Cat.find((err, cats) => {
        console.log(cats);
        res.render('cats', {cats: cats});
    });
});

// post new cats and save them in the database
app.post('/cats', function (req, res) {
    console.log(req.body.name);

    let catname = req.body.name;
    let newCat = new Cat({name: catname});

    newCat.save((err, cat) => {
        if (err) {
            console.error(err);
        }
        console.log(`Cat ${req.body.name} saved to database.`)
        res.redirect('/cats');
    });
});

Ahora, reinicie el servidor y visite localhost:3000/cats. Al igual que para la aplicación Hapi, ahora veremos un espacio vacío con un formulario en la parte superior. Podemos ingresar nombres de gatos. La vista se actualizará y nos mostrará una lista de gatos ahora guardados en nuestra base de datos MongoDB.

Este es un caso en el que la configuración Express fue un poco más compleja que el equivalente de Hapi. Sin embargo, tenga en cuenta que en realidad no tuvimos que hacer demasiado con Hapi o Express para configurar la conexión de la base de datos; la mayor parte del trabajo aquí fue para las rutas GET y POST.

Elegir entre Hapi y Express

Como vimos, Hapi y Express tienen mucho en común. Ambos tienen una base de usuarios activa, y Hapi está siendo ampliamente adoptado por equipos de grandes empresas. Sin embargo, Express sigue superando a la mayoría de los otros marcos en adopción, especialmente para equipos más pequeños.

Hapi tiene sentido si tiene una idea de aplicación bien definida que se ajuste a valores predeterminados sensibles para el enfoque de programación. Hapi adopta un enfoque particular para cosas como la seguridad de las aplicaciones, y su sistema de plugins no es tan extenso como el ecosistema de middleware Express. Si es un equipo grande, las convenciones de Hapi pueden resultar útiles para mantener su código en condiciones de mantenimiento.

Express, por otro lado, funciona mejor si busca flexibilidad y toma la mayoría de las decisiones de desarrollo por su cuenta. Por ejemplo, puede haber diez middleware diferentes que hagan lo mismo en el ecosistema Express. Depende de usted considerar entre muchas opciones y hacer su elección. Express es mejor si tiene una aplicación y no está seguro de la dirección exacta que tomará. Independientemente de las contingencias que surjan, el gran ecosistema Express proporcionará las herramientas para que esto suceda.

Aprende más

Puede obtener un gran comienzo visitando los sitios web respectivos de cada marco:

De cualquier manera, lo más importante es que realmente los pruebe, trabaje con algunos ejemplos y decida por su cuenta cuál es el mejor para usted.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad