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

Lo más probable es que ya haya 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, haremos...

Lo más probable es 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 marcos uno a uno y exploraremos las diferencias en la experiencia del desarrollador.

Similitudes y diferencias entre Hapi y Express {#similitudes y diferencias entre Hapi y Express}

Tanto Expresar como Hapi pretenden 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 para 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 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" de forma predeterminada que Express. Por ejemplo, al analizar la carga útil de formularios a través de solicitudes "POST", con Express, debe incluir el body-parser software intermedio. Luego lo usa para analizar la carga útil de 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 están repletas en Hapi.

Servidor básico y enrutamiento

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

Nuestras dos aplicaciones usarán JavaScript ES6.

Cree dos directorios, hapiapp y expressapp.

Dentro de cada uno, ejecute el comando:

1
$ 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:

1
$ npm install [correo electrónico protegido] --save

Luego crea un archivo index.js con los siguientes contenidos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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: '/',
    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 "Hello World" 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 inclinada "/".

Desde el directorio hapiapp, ejecute la aplicación usando el siguiente comando y pruebe las URL que se indican arriba:

1
$ node index.js

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

1
$ npm install [correo electrónico protegido] --save

Luego crea un archivo index.js con los siguientes contenidos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// expressapp/index.js

const express = require('express');

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

// basic routes
app.get('/', (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 nuestro directorio expressapp, ejecute el comando:

1
$ node index.js

Visite las rutas dadas anteriormente, excepto que ahora estamos ejecutando en localhost: 3000 en lugar de localhost: 8000.

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

Trabajo 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. Hapi, sin embargo, funciona con complementos que brindan funcionalidad de middleware.

Un ejemplo de middleware son los tokens CSRF, que ayudan a prevenir los ataques de piratería Cross-Site Request Forgery (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 en busca del 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 complemento, [Miga] (https://github.com/hapijs/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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
'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 más o menos equivalente usando csurf.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 las implementaciones de otras funciones comunes, como la autenticación. Puede encontrar una lista de algunos de los middleware de Express en este directorio de software intermedio. Para Hapi, existe una [biblioteca de complementos de Hapi] comparable (https://hapijs.com/plugins).

Servicio de imágenes y activos estáticos

El servicio de archivos estáticos es notablemente similar entre Hapi y Express. Hapi usa el complemento Inerte, mientras que Express usa el expreso.estático construido -en el software intermedio.

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

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

Imagen de ejemplo{.img-responsive}

You'll also want to install [correo electrónico protegido] using:

1
$ npm install [correo electrónico protegido] --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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 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');
        }
    });
});

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

Sirviendo archivos estáticos{.img-responsive}

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:

1
2
3
4
5
6
// 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 servida por Express.

Serviendo estática en Express{.img-responsive}

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

Uso de motores de plantillas

Tanto Hapi como Express pueden representar y servir plantillas usando motores como Handlebars, Twig, EJS y otros.

En nuestra carpeta hapiapp instala el complemento de visión para soporte de renderizado con:

1
$ npm install visi[correo electrónico protegido] --save

Instale también el motor de plantillas Bigote daliniano con:

1
$ npm install [correo electrónico protegido] --save

Luego vamos a crear una plantilla de manubrios en un nuevo directorio templates 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:

1
2
3
4
5
6
7
8
<!-- 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 gato y representa 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 /cats, y proporciona una lista de gatos para mostrar allí.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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"},
            ]
        });
    }
});

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

Lista de gatos{.img-responsive}

Para replicar esta funcionalidad en Express, primero cree un directorio llamado views dentro del directorio raíz expressapp. Luego cree un archivo Handlebars cats.hbs con contenidos similares a los que teníamos para Hapi.

1
2
3
4
5
6
7
8
<!-- 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:

1
$ npm install [correo electrónico protegido] --save

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 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 tal como estaba para Hapi.

Notará que el ejemplo de Hapi necesitaba un poco más de configuración que el ejemplo de Express. Sin embargo, ambos ejemplos de código son bastante simples 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 involucrar más código repetitivo que Express. Por otro lado, también hace un poco más por usted desde el primer momento que Express, por lo que definitivamente hay compensaciones.

Conexión a una base de datos {#conexión a la base de datos}

MongoDB es una base de datos NoSQL probada en batalla para usar en sus aplicaciones. Con la biblioteca de modelado de objetos mangosta, podemos conectar MongoDB a las aplicaciones Hapi y Express. Para esta parte, querrá tener MongoDB [instalado] (https://docs.mongodb.com/manual/installation/) y ejecutándose en su sistema si aún no lo tiene.

Para ver cómo funciona esto para ambos marcos, ahora actualicemos nuestras aplicaciones de muestra para almacenar objetos cat en una base de datos. Del mismo modo, nuestros listados obtendrán objetos de gatos de la base de datos.

Los pasos que tenemos que seguir son:

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

De vuelta en nuestro directorio hapiapp, instala mongoose con:

1
$ npm install [correo electrónico protegido] --save

Dado que queremos la capacidad de crear nuevos gatos desde nuestra vista /cats, actualice la plantilla de gatos para incluir un formulario para nuevos gatos. Agregue las siguientes líneas arriba de la lista de gatos en hapiapp/templates/cats.html.

1
2
3
4
5
6
7
<!-- hapiapp/templates/cats.html -->

...

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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 obtener 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 hacer estos cambios, reemplazando el viejo código GET /cats:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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 la tecla Entrar, y sus gatos se guardarán en MongoDB. Ingrese algunos gatos y obtendrá una lista de gatos, como se muestra a continuación:

Nuevas listas de gatos{.img-responsive}

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

1
$ npm install [correo electrónico protegido] --save

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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 para Hapi.

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

1
2
3
4
5
6
7
<!-- 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 importing Express en index.js, importe body-parser de la siguiente manera:

1
2
3
4
5
6
// expressapp/index.js

...

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

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

1
2
3
4
5
6
7
8
9
// 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 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 con la aplicación Hapi, ahora veremos un espacio vacío con un formulario en la parte superior. Podemos introducir 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 ejemplo en el que la configuración de Express fue un poco más compleja que el equivalente de Hapi. Sin embargo, tenga en cuenta que en realidad no necesitamos 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, con Hapi siendo ampliamente adoptado por equipos de grandes empresas. Sin embargo, Express continúa 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 ajusta a valores predeterminados razonables para el enfoque de programación. Hapi adopta un enfoque particular en cosas como la seguridad de las aplicaciones, y su sistema de complementos no es tan extenso como el ecosistema de middleware Express. Si usted es un equipo grande, las convenciones de Hapi pueden ser útiles para mantener su código mantenible.

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.

Más información {#más información}

¿Desea obtener más información sobre el desarrollo web en Node.js y crear algunas aplicaciones prácticas de Express.js? Aquí hay algunos cursos que no solo le brindarán una gran experiencia con este marco web, sino que lo mejorarán como ingeniero de pila completa:

De lo contrario, también puede obtener un gran comienzo visitando los respectivos sitios web de cada marco:

De cualquier manera, lo más importante es que realmente los pruebe, analice algunos ejemplos y decida por sí mismo cuál es el mejor para usted. d.