Desarrollo

Programando alrededor de Express en Node.js

Por: Pedro Cruz

Introducción

Al conocer los conceptos básicos para trabajar con Node.js, en este tutorial se revisará algunos conceptos que se utilizan al momento de programar en Node.js y sirven para lograr diferentes objetivos que se necesitan en el momento de crear un servidor web a través de este entorno de ejecución en JavaScript.

Manejo de direccionamiento

En el tutorial anterior conocimos el concepto de direccionmaiento, donde se utiliza Express para responder a las solicitudes de un cliente. También se conocieron los métodos de ruta que se basa en el uso de los métodos del protocolo HTTP y cada uno de ellos tiene una función definida como app.HTTPMETHOD(PATH, HANDLER).

Sin embargo existe un método de direccionamiento conocido como app.all(PATH, HANDLER), cuya función es el acceso a cualquier método HTTP. Su uso está definido cuando se requiere cargar funciones que pueden realizar cualquier operación, como hacer modificaciones a la petición, ejecutar código o finalizar el ciclo de petición-respuesta, este concepto es conocido como "middleware" y se explicará más adelante.

A continuación se tiene un ejemplo utilizando el método app.all() y los resultados utilizando algunos métodos HTTP en peticiones del cliente:

  1. Creamos la aplicación de ejemplo (usando sistema basado en Linux).

    mkdir myapp
    cd myapp
    npm init
    npm install express --save
    touch index.js
    
  2. Programar el código en index.js.

    var express = require('express'); // Uso del módulo de express
    var app = express(); // Se llama a la función express() que retorna un objeto que se almacena en la constante app
    
    
    // Registrar información sobre todas las solicitudes a todas las rutas
    app.all('*', function (req, res, next) {
        console.log('Se recibió una petición');
        console.log('con el método: ' + req.method);
        console.log('y la URL: ' + req.url);
        // Pasa el control al siguiente manejador
        next();
    });
    
    
    /* Se utiliza el método get() que hace referencia
    a una llamada del tipo GET a través del protocolo HTTP */
    app.get('/', function (req, res) {
        res.send('Hola GET');
    });
    
    
    /* Se utiliza el método post() que hace referencia
    a una llamada del tipo POST a través del protocolo HTTP */
    app.post('/', function (req, res) {
        res.send('Hola POST');
    });
    
    
    /* Se utiliza el métodod listen() para iniciar un servidor y realiza la escucha de las conexiones en el puerto que se le ha indicado a través del primer parámetro (3000) */
    app.listen(3000, function () {
    console.log('Escuchando en el puerto 3000');
    });
    
  3. Ejecutar el programa de index.js con node index.js y revisar los resultados ejecutando la dirección localhost:3000/ con los métodos GET y POST a través del programa Postman.

    Respuesta de petición localhost:3000/ con método GET, la parte superior es la respuesta del servidor y la parte inferior es la respuesta del cliente.

    Respuesta de petición localhost:3000/ con método GET

    Respuesta de petición localhost:3000/ con método POST, la parte superior es la respuesta del servidor y la parte inferior es la respuesta del cliente.

    Respuesta de petición localhost:3000/ con método POST

Ya que se conoce el uso de los métodos de ruta, ahora se debe ver como se manejan las vías de acceso de ruta que se utilizan en el envío de la petición. Dentro de los parámetros de los métodos, se encuentra el valor para indicar la ruta, este valor debe compararse con los caracteres de la URL, estos pueden ser un conjunto de caracteres, patrones de una serie de caracteres o expresiones regulares.

Un ejemplo que se ha programado anteriormente son las solicitudes que usan la ruta raíz de la URL ‘/’, sin embargo se pueden ver otros ejemplos que usan diferentes caracteres para procesar la petición del cliente.

/* Esta vía de acceso de ruta coincidirá con las solicitudes a la ruta raíz '/'*/
app.get('/', function (req, res) {
    res.send('Hola root');
});


/* Esta vía de acceso de ruta coincidirá con las solicitudes con la ruta '/development' */
app.get('/development', function (req, res) {
  res.send('Hola development');
});


/* Esta vía de acceso de ruta coincidirá con las solicitudes con la ruta  '/research.development', ya que el guión (-) y el punto (.) se interpretan literalmente en las vías de acceso basadas en series */
app.get('/research.development', function (req, res) {
  res.send('Hola research.development');
});


/* Esta vía de acceso de ruta coincidirá con las rutas '/acd' y '/abcd'*/
app.get('/ab?cd', function(req, res) {
  res.send('Hola acd o abcd');
});


/* Esta vía de acceso de ruta coincidirá con las rutas como '/abcd', '/abbcd', '/abbbcd', etc */
app.get('/ab+cd', function(req, res) {
  res.send('Hola abcd o abbcd o abbbcd');
});


/* Esta vía de acceso de ruta coincidirá con las rutas como '/abcd', '/abxcd', '/abBLABLAcd', '/ab123cd', etc */
app.get('/ab*cd', function(req, res) {
  res.send('Hola abcd o abxcd o abBLABLAcd o ab123cd');
});


/* Esta vía de acceso de ruta coincidirá con con las rutas '/abe' y '/abcde' */
app.get('/ab(cd)?e', function(req, res) {
 res.send('Hola abe o abcde');
});


/* Esta vía de acceso de ruta coincidirá con cualquier valor con una “t” en el nombre de la ruta */
app.get(/t/, function(req, res) {
  res.send('Hola tech o taco o analytics');
});


/* Esta vía de acceso de ruta coincidirá con palabras que termine con "tech" como '/rocktech' o '/infotech', etc */
app.get(/.*tech$/, function(req, res) {
  res.send('Hola rocktech o infotech');
});

Dentro del direccionamiento podemos realizar un control de la ruta, como agregar condiciones o pasar el control a las rutas posteriores si no existe algo que lo mantenga en la ruta actual. Esto se le conoce como "manejadores de rutas" y puede proporcionar varias funciones de devolución de llamada que se comportan como middleware (explicado más adelante) para manejar una solicitud.

Los manejadores de rutas pueden tener la forma de una función, una matriz de funciones o combinaciones de ambas, tomando el primer ejemplo realizado, se puede realizar algunos cambios para explicar el concepto de una mejor forma. El objetivo es crear una matriz de funciones y una función independiente en app.all() para revisar el método HTTP. Si el método es POST, el cliente recibirá un mensaje de ‘Solo método POST’, cualquier otro método recibirá el mensaje de ‘Rejected’.

var express = require('express'); // Uso del módulo de express
var app = express(); // Se llama a la función express() que retorna un objeto que se almacena en la constante app


// Revisar información sobre todas las solicitudes a todas las rutas
var check = function (req, res, next) {
  console.log('Se recibió una petición');
  console.log('con el método: ' + req.method);
  console.log('y la URL: ' + req.url);
  next(); // Pasa el control al siguiente manejador
}


// Pasar la petición si el método HTTP es post y denegar la petición si el método HTTP es get
var accept = function (req, res, next) {
  if (req.method !== 'POST') {
    res.end('Rejected'); // Finaliza el proceso de respuesta
  } else {
    next(); // Pasa el control al siguiente manejador
  }
}


/* Se utiliza el método app.all(), pasa por la matriz de funciones ([check, accept]), la función independiente (function (req, res, next)) y regresa información al cliente */
app.all('/hello', [check, accept], function (req, res, next) {
  console.log('the response will be sent by the next function');
  next(); // Pasa el control al siguiente manejador
}, function (req, res) {
  res.send('Solo método POST');
});


/* Se utiliza el métodod listen() para iniciar un servidor y realiza la escucha de las conexiones en el puerto que se le ha indicado a través del primer parámetro (3000) */
app.listen(3000, function () {
  console.log('Escuchando en el puerto 3000');
});

Si se ejecuta el programa de index.js con node index.js y se revisan los resultados ejecutando la dirección localhost:3000/hello con los métodos GET, PUT y POST a través del programa Postman, obtendremos resultados similares a las siguientes imágenes.

Respuesta de petición localhost:3000/hello con método GET, la parte superior es la respuesta del servidor y la parte inferior es la respuesta del cliente.

Respuesta de petición localhost:3000/hello con método GET

Respuesta de petición localhost:3000/hello con método PUT, la parte superior es la respuesta del servidor y la parte inferior es la respuesta del cliente.

Respuesta de petición localhost:3000/hello con método PUT

Respuesta de petición localhost:3000/hello con método POST, la parte superior es la respuesta del servidor y la parte inferior es la respuesta del cliente.

Respuesta de petición localhost:3000/hello con método POST

Conociendo los temas relacionados a las rutas, se debe revisar "los métodos de respuesta" que ya vimos en los ejemplos anteriores, con el uso de los métodos res.send() y res.end(). Sin embargo, existen más métodos que nos ayudan a enviar una respuesta al cliente y terminar el ciclo de solicitud/respuestas, ya que si no es agregado un método de respuesta, la solicitud del cliente nunca será terminada por el servidor. A continuación enlistamos algunos métodos de respuesta que existen en Express.

Método Descripción
res.download() Solicita un archivo para descargarlo.
res.end() Finaliza el proceso de respuesta.
res.json() Envía una respuesta JSON.
res.redirect() Redirecciona una solicitud.
res.render() Representa una plantilla de vista.
res.send() Envía una respuesta de varios tipos.
res.sendStatus() Establece el código de estado de la respuesta y envía su representación de serie como el cuerpo de respuesta.

Con esta tabla hemos cubierto los componentes que forman el direccionamiento en la comunicación entre un cliente y un servidor.

Finalmente tenemos la manera de estructurar los diferentes métodos de ruta donde podemos encontrar dos casos, el primero es cuando queremos usar la misma via de acceso, donde se puede utilizar en un concepto conocido como "manejador de rutas encadenables", donde se especifíca un único valor de ruta y luego se generan las rutas modulares que se utilizen con estos caracteres.

Si pensamos en una ruta que podamos trabajar con información de usuarios, teniendo las funciones de obtener una lista de usuarios (método get) y crear un nuevo usuario (método post), entonces necesitamos la opción app.route(PATH). En el siguiente código se puede apreciar el ejemplo explicado en este párrafo.

app.route('/book')
  .get(function(req, res) {
    // Código para traer la lista de usuarios.
    // Uso de res.METHOD() para enviar la respuesta al cliente
  })
  .post(function(req, res) {
    // Código para crear un nuevo usuario.
    // Uso de res.METHOD() para enviar la respuesta al cliente
  })

El segundo caso de estructurar los diferentes métodos de ruta es crear módulos de manejadores de rutas que pueden definir de mejor manera una serie de rutas que se refieran a un elemento. En este caso se puede pensar en crear un archivo denominado users.js para crear rutas con información referente a los usuarios de una aplicación.

En este caso es conviene utilizar la clase express.Router() que es una forma de crear un sistema que puede realizar cualquier operación referente a un módulo, como puede ser el manejo de información de usuarios.En el siguiente código se puede apreciar el ejemplo del uso de la aplicación express.Router() en el archivo users.js.

var express = require('express');  // Uso del módulo de express
var router = express.Router(); // Uso de aplicación Router


// Se crea una función que opera al recibir una petición y está especificada en esta ruta
router.use(function timeLog(req, res, next) {
  console.log('Hora de la petición: ', Date.now());
  next(); // Pasa el control al siguiente manejador
});
// Se define la ruta raíz
router.get('/', function(req, res) {
  res.send('Obtener lista de usuarios.');
});
// Se define la ruta "about"
router.get('/about', function(req, res) {
  res.send('Definir una descripción del módulo de usuarios.');
});


module.exports = router;

Se agrega el archivo users.js, como un módulo, en el archivo donde inicia el servidor web, por ejemplo index.js. En index.js se usa el método app.use() para agregar el Router al software intermediario que maneja las rutas como se ve en el siguiente código.

var users = require('./users');
...
app.use('/users', users);

Con esto completamos los temas relacionados con el direccionamiento y los puntos principales de su funcionalidad.

Manejo de middlewares

Un middleware es descrito como el uso de funciones que están relacionadas con operaciones que gestionen una petición o respuesta, estas operaciones puede ser cambios en una petición, ejecución de código, cambios a un objeto pedido o finalizar un ciclo de petición-respuesta.

Una función permite seguir el flujo a otra función mediante la variable conocida como next. Es importante mencionar que si la función no termina el ciclo de petición-respuesta y no invoca a la pieza de código para continuar en otra función (next()), entonces la solicitud no podrá terminarse de ejecutar y quedará corriendo en el servidor.

La carga de las funciones del middleware se ejecutan cumpliendo el orden en el que se declaran en el archivo que se utiliza para iniciar el servidor, para su uso, se especifica la función use() en un objeto de Express para utilizar el middleware. En el ejemplo para revisar el método HTTP, el uso de funciones de middleware en las variables check y accept pueden aplicarse utilizando este concepto.

var express = require('express'); // Uso del módulo de express
var app = express(); // Se llama a la función express() que retorna un objeto que se almacena en la constante app


// Revisar información sobre todas las solicitudes a todas las rutas
var check = function (req, res, next) {
  console.log('Se recibió una petición');
  console.log('con el método: ' + req.method);
  console.log('y la URL: ' + req.url);
  next(); // Pasa el control al siguiente manejador
}


// Pasar la petición si el método HTTP es post y denegar la petición si el método HTTP es get
var accept = function (req, res, next) {
  if (req.method !== 'POST') {
    res.end('Rejected'); // Finaliza el proceso de respuesta
  } else {
    next(); // Pasa el control al siguiente manejador
  }
}


app.use(check); // Primero revisa cada solicitud


app.use(accept); // Despues pasa solicitudes con el método POST


/* Se utiliza el método app.post() y regresa información al cliente */
app.post('/', function (req, res) {
  res.send('Solo método POST');
});


/* Se utiliza el método app.post() y regresa información al cliente */
app.post('/tech', function (req, res) {
  res.send('Solo método POST');
});


/* Se utiliza el métodod listen() para iniciar un servidor y realiza la escucha de las conexiones en el puerto que se le ha indicado a través del primer parámetro (3000) */
app.listen(3000, function () {
  console.log('Escuchando en el puerto 3000');
});

La documentación de Express explica que dividen los tipos de middleware en 5 diferentes: nivel de aplicación, nivel de direccionador, manejo de errores, funciones incorporadas y funciones de terceros.

Nivel de aplicación: Es el tipo de middleware que se enlaza con el objeto de aplicación de Express mediante el uso de funciones como app.use() y app.METHOD() donde METHOD es el método HTTP que se utiliza en la función como app.get(), app.post(), etc. Es importante que conocer que si app.use() no recibe un parámetro que especifique la vía de acceso, esta función recibirá cualquier petición por parte del cliente.

Nivel de direccionador: Es el tipo de middleware que se enlaza con una instancia de express.Router() mediante el uso de funciones como router.use() y router.METHOD(). La opción de router.use() maneja un comportamiendo similar a la función de app.use().

Manejo de errores: Es el tipo de middleware que requiere una función con cuatro argumentos, estos se definen como error, petición, repsuesta y siguiente. Encontramos ejemplos como manejo de status de error 500. La documentación de Express indica que este tipo de middleware se defina al final, después de otras llamadas a funciones que se necesitan procesar la petición del cliente.

Funciones incorporadas: Es el tipo middleware que se encuentra dentro de la biblioteca de Express para agregar funcionalidad a la aplicación, por ejemplo, existe la función express.static para servir elementos estáticos como imágenes, archivos, etc.

Funciones de terceros: Es el tipo middleware que es desarrollado por terceros para realizar diferentes objetivos dentro de la aplicación. Buscar añadir funcionalidad a la aplicaciones de Express y se tiene ejemplos como el análisis de cookies con el módulo cookie-parser. Normalmente, estos módulos se instalan mediante el uso de npm.

Con lo explicado anteriormente, se genera el conocimiento referente al uso del middleware y en próximos articulos se verán ejemplos de su funcionamiento.

Servicio de archivos estáticos

Dentro de las funcionalidades que maneja Express, se encuentra la opción de servir archivos estáticos como imágenes o archivos en algún formato, esto es gracias al middleware conocido como express.static. La forma de invocarlo dentro de una aplicación con Express es mediante el uso de app.use(express.static(‘nombre del directorio’)). Si dejamos este código descrito anteriormente, se va utilizar la ruta relativa a donde se está ejecutando el archivo y por defecto sirve los archivos desde la raíz del nombre de directorio, si tiene subcarpetas, entonces estas será parte de la ruta que se requiera en el argumento para obtener dicho elemento.

Si hacemos el ejemplo de que tenemos una carpeta que se llama ‘archivos’ y esta carpeta contiene otra carpeta llamada ‘info’ pero dentro está el archivo ‘respuesta.json’, entonces al ejecutar en un servidor localhost:3000, un archivo index.js con el código app.use(express.static(‘archivos’)), entonces se procederá a ejecutar la petición de la siguiente manera: . Funciona con el método get del protocolo HTTP y te regresa lo que contiene el archivo ‘respuesta.json’.

Existen otros dos parámetros que se pueden agregar al código, uno es el uso de crear un prefijo de vía de acceso virtual que no existe en el sistema de archivos, sin embargo se puede inidicar en la ruta de la petición y el segundo es utilizar la ruta de acceso absoluta del directorio (__dirname), donde se encuentra el servidor y partir de ahí para generar la ruta a servir los archivos estaticos. Con esto, se genera el código como app.use(‘/rutavirtual’, express.static(__dirname + ‘/archivos’)); y si usamos el ejemplo anterior, la petición se tendría que hacer de la siguiente manera: . Con esto se observa que puede servir los archivos que estan en el directorio ‘archivos’ de la ruta con prefijo /rutavirtual.

Un posible código para el servicio de archivos estáticos se muestra a continuación:

var express = require('express'); // Uso del módulo de express
var app = express(); // Se llama a la función express() que retorna un objeto que se almacena en la constante app


 // Servidor de archivos estáticos con la ruta denominada como '/rutavirtual' y se encuentran en el directorio '/archivos'
app.use('/rutavirtual', express.static(__dirname + '/archivos'));


/* Se utiliza el métodod listen() para iniciar un servidor y realiza la escucha de las conexiones en el puerto que se le ha indicado a través del primer parámetro (3000) */
app.listen(3000, function () {
  console.log('Escuchando en el puerto 3000');
});

Con este tema, se puede ver los beneficios de la aplicación de un middleware para servir archivos estáticos a los clientes.

Conclusión

En este artículo se profundizó en algunas de las características que forman la esencia de utilizar un framework como Express, para este caso se presentó los componentes que maneja el direccionamiento, así como la compresión de los middlewares y su aplicación en un objetivo como la construcción de un servidor de archivos estáticos. En el siguiente tutorial se trabajará con más funcionalidades de este marco de trabajo y como se puede ir construyendo una aplicación web con Node.js.

Pedro Cruz (@pedcru)

Av. Cerro Gordo del Campestre 201
Int. 303
Col. Las Quintas
León, Gto.
C.P. 37125

ROCKTECH R+D © 2020