Node.js

Los módulos de Node.js

     

Una de las principales características de Node.js es que está diseñado de forma modular: su funcionamiento se organiza en módulos o paquetes que podemos incluir en nuestros proyectos según lo necesitemos.

Podemos distinguir tres tipos de módulos en Node.js:

Gracias a este sistema, en un proyecto Node.js solo cargamos lo que realmente necesitamos, manteniendo el código más limpio, ligero y mantenible.

En esta unidad aprenderemos a trabajar con los tres tipos de módulos y a utilizar las herramientas que nos ofrece el ecosistema de Node.js para gestionarlos.

1. Utilizar módulos del núcleo de Node

Como comentábamos, el propio entorno de Node,js incorpora una serie de módulos nativos que podemos usar directamente en nuestras aplicaciones, sin necesidad de instalar nada adicional. En la documentación oficial puedes consultar la lista completa.

Para usar un módulo (nativo o de terceros), podemos importarlo en nuestro código de dos maneras:

1.1 Ejemplo con CommonJS (require)

Vamos a crear un archivo llamado listado.js en nuestro proyecto de “ProyectosNode/Pruebas/PruebasSimples”. El programa listará todos los archivos y carpetas de una ruta usando el módulo fs (file system).

En Node.js es habitual que una misma función se pueda utilizar de formas distintas:

A continuación vemos el primer caso, la versión síncrona:

const ruta = '/Users/may';
const fs = require('fs');
fs.readdirSync(ruta).forEach(fichero => {
    console.log(fichero);
});

Si ejecutamos este programa obtendremos el listado de la carpeta indicada. Antes de ejecutarlo, recuerda cambiar el valor de la constante ruta en el código por el de una carpeta válida en tu sistema.

Observa que en el código se han declarado dos constantes (const), en lugar de variables (var/let), ya que no necesitamos manipular o modificar el código que se almacenará en ellas una vez cargadas (no vamos a cambiar la ruta, ni el contenido del módulo fs en nuestro código).

Veamos el ejemplo equivalente al anterior usando la versión asíncrona (callback):

const ruta = '/Users/may';
const fs = require('fs');

fs.readdir(ruta, (err, archivos) => {
  if (err) {
    console.error("Error al leer la carpeta:", err);
    return;
  }
  
  archivos.forEach(fichero => {
    console.log(fichero);
  });
});

console.log("El programa sigue ejecutándose...");

La función readdir() recibe dos argumentos: en primer lugar, la ruta, que indica la carpeta que queremos leer, y en segundo lugar, un callback, que es la función que se ejecutará cuando Node.js termine de procesar la lectura. Este callback recibe a su vez dos parámetros: err, que contendrá un error en caso de que la operación falle (por ejemplo, si la ruta no existe), y archivos, que será un array con los nombres de los archivos y subcarpetas de la ruta indicada cuando la operación se complete correctamente.

En este caso, primero se muestra el mensaje “El programa sigue ejecutándose…“. Después, cuando readdir() termina de procesar la carpeta, se ejecuta la función que imprime el listado de archivos.

Por último, veamos el equivalente utilizando la versión moderna (promesas + async/await):

const ruta = '/Users/may';
const fs = require('fs');

async function listar() {
  try {
    const archivos = await fs.promises.readdir(ruta);
    console.log(archivos);
  } catch (err) {
    console.error(err);
  }
}

listar();

En este caso, la función listar() está definida como asíncrona gracias a la palabra reservada async. Esto permite utilizar dentro de ella la instrucción await, que detiene la ejecución de la función hasta que la promesa se resuelve.

Además, observa que en este caso estamos usando fs.promises.readdir() en lugar de fs.readdir(). Esto se debe a que el módulo fs ofrece varias formas de trabajar: la versión síncrona (fs.readdirSync), la versión asíncrona con callback (fs.readdir) y, dentro de la propiedad especial promises, la versión que devuelve una promesa (fs.promises.readdir). De esta forma Node.js agrupa en un mismo módulo (fs) todas las alternativas disponibles, y podemos elegir la que más nos convenga según el tipo de aplicación que estemos desarrollando.

A diferencia de las versiones anteriores, esta implementación no bloquea el programa como ocurre con readdirSync, tampoco requiere el uso de callbacks como en el caso de readdir, y además ofrece un código mucho más legible y fácil de mantener gracias a la sintaxis moderna de async/await.

1.2 Ejemplo con ES Modules (import)

En proyectos modernos es habitual usar ES Modules, que nos permite importar módulos con la instrucción import en lugar de require.

Para poder usar esta sintaxis, debemos configurar nuestro proyecto de una de estas dos maneras:

Veamos los mismos ejemplos de antes, pero escritos con import.

Versión síncrona (readdirSync)

import { readdirSync } from 'fs';

const ruta = '/Users/may';
const archivos = readdirSync(ruta);

archivos.forEach(fichero => {
  console.log(fichero);
});

Aquí readdirSync se importa directamente desde el módulo fs, y el programa queda bloqueado hasta que obtiene el listado de archivos.

Versión asíncrona con callback (readdir)

import { readdir } from 'fs';

const ruta = '/Users/may';

readdir(ruta, (err, archivos) => {
  if (err) {
    console.error("Error al leer la carpeta:", err);
    return;
  }
  
  archivos.forEach(fichero => {
    console.log(fichero);
  });
});

console.log("El programa sigue ejecutándose...");

Versión moderna con promesas (fs/promises)

import { readdir } from 'fs/promises';

const ruta = '/Users/may';

const listarArchivos = async () => {
  try {
    const archivos = await readdir(ruta);
    console.log(archivos);
  } catch (err) {
    console.error("Error al leer la carpeta:", err);
  }
};

listarArchivos();

Observa que aquí, al usar ES Modules, importamos directamente la función readdir desde fs/promises. A diferencia de CommonJS, donde debemos acceder mediante fs.promises.readdir, aquí la tenemos disponible con una sola instrucción, lo que hace el código más limpio y directo.

Esta es la forma actual y recomendada de hacerlo en proyectos Node.js modernos, ya que aprovecha el estándar de ES Modules y la potencia de las promesas con async/await.

Ejercicio 1:

Crea una carpeta llamada “SaludoUsuario” en tu espacio de trabajo, en la carpeta de “Ejercicios”. Añade un archivo llamado saludo.js. Echa un vistazo en la API de Node al módulo os, y en concreto al método userInfo(). Utilízalo para hacer un programa que salude al usuario que ha accedido al sistema operativo. Por ejemplo, si el usuario es “May”, debería decir “Hola May”. Ejecuta el programa en el terminal para comprobar su correcto funcionamiento.

El ejercicio puede resolverse de distintas maneras: primero utilizando CommonJS con require, después con ES Modules usando import, que es la forma moderna y recomendada en los proyectos actuales de Node.js.

Opcionalmente, haz que el programa para que muestre no solo el nombre del usuario, sino también su directorio personal (homedir).

NOTA: el método console.log admite un número indefinido de parámetros, y los muestra concatenados uno tras otro separados por un espacio. Estas instrucciones son equivalentes y producen el mismo resultado:

console.log("Hola " + nombre);
console.log("Hola", nombre);
console.log(`Hola ${nombre}`);

2. Incluir nuestros propios módulos

Además de utilizar módulos nativos, en Node.js también podemos crear nuestros propios módulos. Esto nos permite dividir una aplicación en varios archivos, de forma que el código esté mejor organizado, sea más fácil de mantener y podamos reutilizar funciones en distintos lugares.

Para entenderlo mejor, vamos a crear un pequeño proyecto llamado “PruebasRequire” en nuestra carpeta de “Pruebas”. Empezaremos con dos archivos:

2.1 CommonJS

2.1.1 Importar módulos con require

Para incluir un archivo nuestro dentro de otro en CommonJS, usamos la instrucción require.

const utilidades = require('./utilidades.js');

El prefijo ./ indica que estamos importando un archivo local que está en el mismo directorio que principal.js. Si no lo ponemos, Node.js buscará un módulo nativo (como fso os) o un módulo de terceros instalado en node_modules (como veremos más adelante).

Es posible también suprimir la extensión del archivo en el caso de archivos JavaScript, por lo que la instrucción anterior sería equivalente a esta otra (Node.js sobreentiende que se trata de un archivo JavaScript):

const utilidades = require('./utilidades');

2.1.2 Exportar contenido con module.exports

El contenido del archivo utilidades.js debe tener una estructura determinada. Si, por ejemplo, el archivo tiene este contenido:

console.log('Entrando en utilidades.js');

Entonces el mero hecho de incluirlo con require mostrará por pantalla el mensaje “Entrando en utilidades.js” al ejecutar la aplicación. Es decir, cualquier instrucción directa que contenga el archivo incluido se ejecuta al incluirlo. Lo normal, no obstante, es que este archivo no contenga instrucciones directas, sino una serie de propiedades y métodos que puedan ser accesibles desde el archivo que lo incluye.

Supongamos que el archivo utilidades.js tiene unas funciones matemáticas para sumar y restar dos números y devolver el resultado. Algo así:

let sumar = (num1, num2) => num1 + num2;
let restar = (num1, num2) => num1  num2;

Lo lógico sería pensar que, al incluir este archivo con require desde principal.js, podamos acceder a las funciones sumar y restar que hemos definido… pero no es así. El contenido de utilidades.js no es accesible automáticamente desde fuera.

Para poder hacer los métodos o propiedades de un archivo visibles desde otro que lo incluya, debemos añadirlos como elementos del objeto module.exports. Así, las dos funciones anteriores se deberían definir de esta forma:

module.exports.sumar = (num1, num2) => num1 + num2;
module.exports.restar = (num1, num2) => num1  num2;

Es habitual definir un objeto en module.exports, y añadir dentro todo lo que queramos exportar. De esta forma, tendremos por un lado el código de nuestro módulo, y por otro la parte que queremos exportar. El módulo quedaría así, en este caso:

let sumar = (num1, num2) => num1 + num2;
let restar = (num1, num2) => num1  num2;

module.exports = {
    sumar: sumar,
    restar: restar
};

En cualquier caso, ahora sí podemos utilizar estas funciones desde el programa principal.js:

const utilidades = require('./utilidades');
console.log(utilidades.sumar(3, 2));

El objeto module.exports admite tanto funciones como atributos o propiedades. Por ejemplo, podríamos definir una propiedad para almacenar el valor del número “pi”:

module.exports = {
    pi: 3.1416,
    sumar: sumar,
    restar: restar
};

… y acceder a ella desde el programa principal:

console.log(utilidades.pi);

2.2 ES Modules

2.2.1 Importar módulos con import

En proyectos modernos, Node.js permite usar ES Modules. En este caso empleamos import en lugar de require:

import * as utilidades from './utilidades.js';

Igual que en CommonJS, “./” indica que el archivo es local. Sin embargo, en ES Modules, la extensión .js no se puede omitir, debe escribirse siempre.

2.2.2 Exportar contenido con export

En ES Modules tenemos dos opciones, usar exportaciones con nombre o una exportación por defecto.

a) Exportaciones con nombre

En el archivo utilidades.js tendremos:

// utilidades.js
export const sumar = (a, b) => a + b;
export const restar = (a, b) => a - b;
export const pi = 3.1416;

Y en principal.js:

import { sumar, restar, pi } from './utilidades.js';

console.log(sumar(3, 2));   // 5
console.log(restar(5, 1));  // 4
console.log(pi);            // 3.1416

b) Exportaciones por defecto

En este caso, exportamos un único objeto que contiene todas las funciones y propiedades que queremos usar desde fuera.

Así, en el archivo utilidades.js tendremos:

// utilidades.js
const sumar = (a, b) => a + b;
const restar = (a, b) => a - b;
const pi = 3.1416;

export default {
  sumar,
  restar,
  pi
};

Y en principal.js:

// principal.js
import utilidades from './utilidades.js';

console.log(utilidades.sumar(3, 2)); // 5
console.log(utilidades.restar(5, 1)); // 4
console.log(utilidades.pi); // 3.1416

Con export default no usamos llaves { } al importar, porque todo llega agrupado en un único objeto.

2.3 Agrupar módulos

2.3.1 Incluir carpetas enteras

En el caso de que nuestro proyecto contenga varios módulos relacionados, es recomendable organizarlos en carpetas siguiendo una nomenclatura específica. Los pasos a seguir son:

Desde el programa principal (u otro lugar que necesite incluir la carpeta entera), incluir el nombre de la carpeta. Automáticamente se localizará e incluirá el archivo index.js, con todos los módulos que éste haya incluido dentro.

Veamos un ejemplo: vamos a nuestra carpeta “PruebasRequire” creada en el ejemplo anterior, y crea una carpeta llamada “idiomas”. Dentro, crea estos tres archivos, con el siguiente contenido:

Archivo es.js

module.exports = {
    saludo : "Hola"
};

Archivo en.js

module.exports = {
    saludo : "Hello"
};

Archivo index.js

const en = require('./en');
const es = require('./es');

module.exports = {
    es : es,
    en : en
};

Ahora, en la carpeta raíz de “PruebasRequire” crea un archivo llamado saludo_idioma.js, con este contenido:

const idiomas = require('./idiomas');

console.log("English:", idiomas.en.saludo);
console.log("Español:", idiomas.es.saludo);

Como puedes ver, desde el archivo principal sólo hemos incluido la carpeta, y con eso automáticamente incluimos el archivo index.js que, a su vez, incluye a los demás. Una vez hecho esto, y tal y como hemos exportado las propiedades en index.js, podemos acceder al saludo en cada idioma.

2.3.2 Incluir archivos JSON

Node.js permite incluir directamente archivos JSON como si fueran módulos. Los archivos JSON son especialmente útiles, como veremos, para definir cierta configuración básica (no encriptada) en las aplicaciones, además de para enviar información entre partes de la aplicación (lo que veremos también más adelante).

Por ejemplo, y siguiendo con el ejemplo anterior, podríamos sacar a un archivo JSON el texto del saludo en cada idioma. Añadamos un archivo llamado saludos.json dentro de nuestra subcarpeta “idiomas”:

{
    "es" : "Hola",
    "en" : "Hello"
}

Después, podemos modificar el contenido de los archivos es.js y en.js para que no pongan literalmente el texto, sino que lo cojan del archivo JSON, incluyéndolo. Nos quedarían así:

Archivo es.js:

const textos = require('./saludos.json');

module.exports = {
    saludo : textos.es
};

Archivo en.js:

const textos = require('./saludos.json');

module.exports = {
    saludo : textos.en
};

La forma de acceder a los textos desde el programa principal no cambia, pero ahora la información está centralizada en un único archivo JSON en lugar de estar repartida en varios archivos JavaScript. Esto facilita el mantenimiento: si hay una errata o queremos actualizar un texto, basta con modificar el JSON una sola vez. Además, evitamos el problema de las magic strings (cadenas escritas “a mano” en el código, que son más difíciles de localizar y de reutilizar).

NOTA: Los archivos JSON solo pueden contener datos (números, cadenas, booleanos, arrays u objetos), pero nunca funciones ni lógica de programación. Por eso se utilizan principalmente para configuración y para almacenar o intercambiar información, pero no para definir comportamiento.

2.4. Detalles avanzados

Para finalizar con este subapartado de inclusión de módulos locales de nuestra aplicación (o división de nuestra aplicación en diversos ficheros fuente, según cómo queramos verlo), conviene tener en cuenta un par de matices adicionales.

2.4.1. Rutas relativas y __dirname

Hasta ahora, cuando hemos empleado la instrucción require para incluir un módulo de nuestro proyecto, hemos partido de la carpeta actual. Por ejemplo:

const utilidades = require('./utilidades');

Este código funcionará siempre que ejecutemos la aplicación Node.js desde su misma carpeta:

node principal.js

Pero si estamos en otra carpeta y ejecutamos la aplicación desde allí…

node /Users/May/Proyectos/PruebasRequire/principal.js

… entonces require hará referencia a la carpeta desde donde estamos ejecutando, y no encontrará el archivo “utilidades.js”, en este caso. Para evitar este problema, podemos emplear la propiedad __dirname, que hace referencia a la carpeta del módulo que se está ejecutando (principal.js, en este caso):

const utilidades = require(__dirname + '/utilidades');

2.4.2. Diferencia entre exports y module.exports en CommonJS

En Node.js, lo que realmente se devuelve al usar require es module.exports. Ese es el objeto “oficial” que se devuelve cuando importamos un módulo.

Sin embargo, en muchos ejemplos de Internet verás también exports. Esto puede generar confusión, porque exports es simplemente un atajo (alias) que apunta a module.exports.

Al inicio de cada archivo, Node.js hace internamente algo así:

let module = { exports: {} };
let exports = module.exports;

De este modo, tanto exports como module.exports apuntan inicialmente al mismo objeto. Mientras no se reasigne, añadir propiedades a exports es equivalente a añadirlas a module.exports.

El siguiente ejemplo muestra el uso correcto de exports:

// utilidades.js
exports.sumar = (a, b) => a + b;
exports.restar = (a, b) => a - b;

// principal.js
const util = require('./utilidades');
console.log(util.sumar(3, 2)); // 5

Funciona porque al hacer exports.sumar = ..., en realidad estamos añadiendo propiedades a module.exports, que es lo que se devuelve.

En cambio, si reasignamos exports obtendremos un error de ejecución porque al hacer exports = { ... }, rompemos la conexión entre exports y module.exports y, por tanto, el objeto importado estará vacío y no tendrá la función.

Este otro ejemplo muestra un uso incorrecto de exports:

// utilidades.js
exports = {
  sumar: (a, b) => a + b
};

// principal.js
const util = require('./utilidades');
console.log(util.sumar(3, 2)); // Error: util.sumar is not a function

Ahora exports apunta a un nuevo objeto, pero require sigue devolviendo el viejo module.exports, que está vacío.

La moraleja de todo esto es que, en principio, exports y module.exports sirven para lo mismo siempre que no las reasignemos. Para evitar errores, lo recomendable es usar siempre module.exports.

Ejercicio 2:

Para realizar este ejercicio, nos basaremos en el ejercicio “Promesas” de la sesión anterior, donde gestionábamos las personas de un vector mediante unos métodos que insertaban o borraban datos del mismo, y devolvían una promesa con el resultado.

Copia esa carpeta y renómbrala a “Modularizar”. Lo que vamos a hacer en este ejercicio es dividir el código en dos archivos:

Ejecuta el programa para verificar que las dependencias con el módulo se han establecido correctamente, y los datos se insertan y borran del vector de forma satisfactoria.

3. Módulos de terceros. El gestor npm

Existen numerosos módulos hechos por terceros que pueden ser añadidos y utilizados en nuestras aplicaciones, como por ejemplo el módulo mongoose para acceso a bases de datos MongoDB, o el módulo express para incorporar el framework Express.js a nuestro proyecto, y desarrollar aplicaciones web con él, como veremos en sesiones posteriores. Estos módulos de terceros se instalan a través del gestor npm.

npm (Node Package Manager) es un gestor de paquetes para JavaScript, y se instala automáticamente al instalar Node.js. Podemos comprobar que lo tenemos instalado, y qué versión concreta tenemos, mediante el comando:

npm -v

aunque también nos servirá el comando npm --version.

Inicialmente, npm se pensó como un gestor para poder instalar módulos en las aplicaciones Node, pero se ha convertido en mucho más que eso, y a través de él podemos también descargar e instalar en nuestras aplicaciones otros módulos o librerías que no tienen que ver con Node, como por ejemplo Bootstrap o jQuery. Así que actualmente es un enorme ecosistema de librerías open-source, que nos permite centrarnos en las necesidades específicas de nuestra aplicación, sin tener que “reinventar la rueda” cada vez que necesitemos una funcionalidad que ya han hecho otros antes. El registro de librerías o módulos gestionado por NPM está en la web npmjs.com.

Podemos consultar información sobre alguna librería en particular, consultar estadísticas de cuánta gente se la descarga, e incluso proporcionar nosotros nuestras propias librerías si queremos. Por ejemplo, esta es la ficha de la librería express, que emplearemos más adelante:

La opción más habitual de uso de npm es instalar módulos o paquetes en un proyecto concreto, de forma que cada proyecto tenga sus propios módulos. Sin embargo, en algunas ocasiones también nos puede interesar (y es posible) instalar algún módulo de forma global al sistema. Veremos cómo hacer estas dos operaciones.

3.1. Instalar módulos locales a un proyecto

En este apartado veremos cómo instalar módulos de terceros de forma local a un proyecto concreto. Haremos pruebas dentro de un proyecto llamado “PruebaNPM” en nuestra carpeta de “ProyectosNode/Pruebas”, cuya carpeta podemos crear ya.

3.1.1. El archivo “package.json”

La configuración básica de los proyectos Node se almacena en un archivo JSON llamado package.json. Este archivo se puede crear directamente desde línea de comandos, utilizando una de estas dos opciones (debemos ejecutarla en la carpeta de nuestro proyecto Node):

{
    "name": "PruebaNPM",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
}

Al final de todo el proceso, tendremos el archivo en la carpeta de nuestro proyecto. En él añadiremos después (de forma manual o automática) los módulos que necesitemos, y las versiones de los mismos, como explicaremos a continuación.

NOTA: al generar el archivo package.json, podemos observar que el nombre de programa principal (entry point) que se asigna por defecto a la aplicación Node es index.js. Es habitual que el fichero principal de una aplicación Node se llame así, o también app.js, como veremos en posteriores ejemplos, aunque no es obligatorio llamarlos así.

3.1.2. Añadir módulos al proyecto y utilizarlos

Para instalar un módulo externo en un proyecto determinado, debemos abrir un terminal y situarnos en la carpeta del proyecto. Después, escribimos el siguiente comando:

npm install nombre_modulo

donde nombre_modulo será el nombre del módulo que queramos instalar. Podemos instalar también una versión específica del módulo añadiéndolo como sufijo con una arroba al nombre del módulo. Por ejemplo:

npm install nombre_modulo@1.1.0

Vamos a probar con un módulo sencillo y muy utilizado (tiene millones de descargas semanalmente), ya que contiene una serie de utilidades para facilitarnos el desarrollo de nuestros proyectos. Se trata del módulo lodash, que podéis consultar en la web citada anteriormente (aquí). Para instalarlo, escribimos lo siguiente:

npm install lodash

Algunas puntualizaciones antes de seguir:

{
    "name": "pruebanpm",
    ...
    "dependencies": {
        "lodash": "^4.17.15"
    }
}

Para poder utilizar el nuevo módulo, procederemos de la misma forma que para utilizar módulos predefinidos de Node: emplearemos la instrucción require con el nombre original del módulo. Por ejemplo, vamos a editar un archivo index.js en la carpeta “PruebaNPM” que venimos editando en estos últimos pasos, y añadimos este código que carga el módulo “lodash” y lo utiliza para eliminar un elemento de un vector:

const lodash = require('lodash');
console.log(lodash.difference([1, 2, 3], [1]));

NOTA: si buscáis documentación o ejemplos de uso de esta librería en Internet, es habitual que el nombre de variable o constante donde se carga (en la línea require) sea un simple símbolo de subrayado (eso es lo que significa low dash en inglés), con lo que el ejemplo anterior quedaría así:

const _ = require('lodash');
console.log(_.difference([1, 2, 3], [1]));

Si ejecutamos este ejemplo desde el terminal, obtendremos lo siguiente:

node index.js
[ 2, 3 ]

Ejercicio 3:

Crea una carpeta llamada “Lodash” en tu espacio de trabajo, en la carpeta de “Ejercicios”. Dentro, crea un archivo package.json utilizando el comando npm init visto antes. Deja los valores por defecto que te plantea el asistente, y pon tu nombre como autor.

Después, instala el paquete lodash como se ha explicado en un ejemplo anterior, y consulta su documentación aquí, para hacer un programa en un archivo index.js que, dado un vector de nombres de personas, los muestre por pantalla separados por comas. Deberás definir a mano el array de nombres dentro del código. Por ejemplo, para el array ["Nacho", "Ana", "Mario", "Laura"], la salida del programa deberá ser:

Nacho,Ana,Mario,Laura

NOTA: revisa el método join dentro de la documentación de “lodash”, puede serte muy útil para este ejercicio.

3.1.3. Desinstalar un módulo

Para desinstalar un módulo (y eliminarlo del archivo package.json, si existe), escribimos el comando siguiente:

npm uninstall nombre_modulo

3.1.4. Orden de inclusión de los módulos

Hemos visto cómo incluir en una aplicación Node tres tipos de módulos:

Aunque no hay una norma obligatoria a seguir al respecto, sí es habitual que, cuando nuestra aplicación necesita incluir módulos de diversos tipos (predefinidos de Node, de terceros y archivos propios), se haga con una estructura determinada.

Básicamente, lo que se hace es incluir primero los módulos de Node y los de terceros, y después (separados por un espacio del bloque anterior), los archivos propios de nuestro proyecto. Por ejemplo:

const fs = require('fs');
const _ = require('lodash');

const utilidades = require('./utilidades');

3.1.5. Algunas consideraciones sobre módulos de terceros

Hemos visto los pasos elementales para poder instalar, utilizar, y desinstalar (si es necesario) módulos de terceros localmente en nuestras aplicaciones. Pero hay algunos aspectos referentes a estos módulos, y la forma en que se instalan y distribuyen, que debes tener en cuenta.

Gestión de versiones

Desde que comenzamos a desarrollar una aplicación hasta que la finalizamos, o en mantenimientos posteriores, es posible que los módulos que la componen se hayan actualizado. Algunas de esas nuevas versiones pueden no ser compatibles con lo que en su día hicimos, o al contrario, hemos actualizado la aplicación y ya no nos sirven versiones demasiado antiguas de ciertos módulos.

Para poder determinar qué versiones o rangos de versiones son compatibles con nuestro proyecto, podemos utilizar la misma sección de “dependencies” del archivo package.json, con una nomenclatura determinada. Veamos algunos ejemplos utilizando el paquete “lodash” del caso anterior:

Existen otros modificadores también, pero con éstos podemos hacernos una idea de lo que podemos controlar. Una vez hayamos especificado los rangos de versiones compatibles de cada módulo, con el siguiente comando actualizamos los paquetes que se vean afectados por estas restricciones, dejando para cada uno una versión dentro del rango compatible indicado:

npm update

Añadir módulos a mano en “package.json”

También podríamos añadir a mano en el archivo “package.json” módulos que necesitemos instalar. Por ejemplo, así añadiríamos al ejemplo anterior la última versión del módulo “express”:

{
    ...
    "dependencies": {
        "lodash": "^4.17.4",
        "express": "*"
    }
}

Para hacer efectiva la instalación de los módulos de este archivo, una vez añadidos, debemos ejecutar este comando en el terminal:

npm install

Automáticamente se añadirán los módulos que falten en la carpeta “node_modules” del proyecto.

Compartir nuestro proyecto

Si decidimos subir nuestro proyecto a algún repositorio en Internet como Github o similares, o dejar que alguien se lo descargue para modificarlo después, no es buena idea subir la carpeta “node_modules”, ya que contiene código fuente hecho por terceras personas, probado en entornos reales y fiable, que no debería poderse modificar a la ligera. Además, la forma en que se estructura la carpeta “node_modules” depende de la versión de npm que cada uno tengamos instalada, y es posible que ocupe demasiado. De hecho, los propios módulos que descargamos pueden tener dependencias con otros módulos, que a su vez se descargarán en una subcarpeta interna.

Por lo tanto, lo recomendable es no compartir esta carpeta (no subirla al repositorio, o no dejarla a terceras personas), y no es ningún problema hacer eso, ya que gracias al archivo package.json siempre podemos (debemos) ejecutar el comando

npm install 

y descargar todas las dependencias que en él están reflejadas. Dicho de otra forma, el archivo package.json contiene un resumen de todo lo externo que nuestro proyecto necesita, y que no es recomendable facilitar con el mismo.

3.2. Instalar módulos globales al sistema

Para cierto tipo de módulos, en especial aquellos que se ejecutan desde terminal como Grunt (un gestor y automatizador de tareas JavaScript) o JSHint (un comprobador de sintaxis JavaScript), puede ser interesante instalarlos de forma global, para poderlos usar dentro de cualquier proyecto.

La forma de hacer esto es similar a la instalación de un módulo en un proyecto concreto, añadiendo algún parámetro adicional, y con la diferencia de que, en este caso, no es necesario un archivo “package.json” para gestionar los módulos y dependencias, ya que no son módulos de un proyecto, sino del sistema. La sintaxis general del comando es:

npm install -g nombre_modulo

donde el flag -g hace referencia a que se quiere hacer una instalación global.

Es importante, además, tener presente que cualquier módulo instalado de forma global en el sistema no podrá importarse con require en una aplicación concreta (para hacerlo tendríamos que instalarlo también de forma local a dicha aplicación).

3.2.1. Ejemplo: nodemon

Veamos cómo funciona la instalación de módulos a nivel global con uno realmente útil: el módulo nodemon. Este módulo funciona a través del terminal, y nos sirve para monitorizar la ejecución de una aplicación Node, de forma que, ante cualquier cambio en la misma, automáticamente la reinicia y vuelve a ejecutarla por nosotros, evitándonos tener que escribir el comando node en el terminal de nuevo. Podéis consultar información sobre nodemon aquí.

Para instalar nodemon de forma global escribimos el siguiente comando (con permisos de administrador):

npm install -g nodemon

Al instalarlo de forma global, se añadirá el comando nodemon en la misma carpeta donde residen los comandos node o npm. Para utilizarlo, basta con colocarnos en la carpeta del proyecto que queramos probar, y emplear este comando en lugar de node para lanzar la aplicación:

nodemon index.js

Automáticamente aparecerán varios mensajes de información en pantalla y el resultado de ejecutar nuestro programa. Ante cada cambio que hagamos, se reiniciará este proceso volviendo a ejecutarse el programa.

Para finalizar la ejecución de nodemon (y, por tanto, de la aplicación que estamos monitorizando), basta con pulsar Control+C en el terminal.

NOTA: si utilizamos el terminal powershell de Visual Studio Code podemos tener problemas para ejecutar Nodemon. En este caso debemos configurar el terminal para que sea de tipo Command Prompt

3.2.2. Desinstalar módulos globales

Del mismo modo, para desinstalar un módulo que se ha instalado de forma global, utilizaremos el comando:

npm uninstall -g nombre_modulo

Ejercicio 4:

Crea una carpeta llamada “Moment” en tu espacio de trabajo, en la carpeta “Ejercicios”. Dentro, crea un archivo package.json con el correspondiente comando npm init visto antes. Deja los valores por defecto que te plantea el asistente, y pon tu nombre como autor.

Instala el paquete moment, una librería para gestión de fechas y tiempos cuya documentación se puede consultar aquí. Define un programa principal en un archivo index.js que incluya dicha librería:

const moment = require('moment');

Una vez incluida, haz lo siguiente:

let ahora = moment();
let antes = moment("07/10/2015", "DD/MM/YYYY");
console.log(moment.duration(fechaNueva.diff(fechaVieja)).years());
if (fechaVieja.isBefore(fechaReciente))...
if (fechaReciente.isAfter(fechaVieja))...

Si no lo has hecho aún, instala el módulo nodemon de forma global al sistema, como se ha explicado en esta sesión. Ejecuta esta aplicación con dicho módulo, y comprueba que todos los datos que se muestran por consola son los esperados. Después, prueba a cambiar alguna fecha (la pasada y/o la futura), y comprueba cómo se ejecuta de nuevo automáticamente y muestra los nuevos resultados.