DEV Community

Pedro Alvarado
Pedro Alvarado

Posted on

Módulos y Gestión de Dependencias en Node.js (2/n)

Guía Completa: Módulos y Gestión de Dependencias en Node.js

Tabla de Contenidos

  1. Sistema de Módulos en Node.js
  2. CommonJS vs ES Modules
  3. Module Caching
  4. El Archivo package.json
  5. Gestores de Paquetes
  6. node_modules y Resolución de Dependencias
  7. Semantic Versioning
  8. Lock Files
  9. Seguridad y Auditorías
  10. Buenas Prácticas
  11. Cuestionario de Entrevista

Sistema de Módulos en Node.js

Node.js implementa un sistema de módulos para organizar y reutilizar código. Cada archivo en Node.js es tratado como un módulo independiente.

¿Qué es un Módulo?

Un módulo es una unidad encapsulada de código que puede exportar funcionalidades y ser importada por otros módulos.

Analogía con Python:

# Python: archivo math_utils.py
def sumar(a, b):
    return a + b

def restar(a, b):
    return a - b

# Importar en otro archivo
from math_utils import sumar
print(sumar(5, 3))
Enter fullscreen mode Exit fullscreen mode

Analogía con Ruby:

# Ruby: archivo math_utils.rb
module MathUtils
  def self.sumar(a, b)
    a + b
  end

  def self.restar(a, b)
    a - b
  end
end

# Importar en otro archivo
require_relative 'math_utils'
puts MathUtils.sumar(5, 3)
Enter fullscreen mode Exit fullscreen mode

Tipos de Módulos en Node.js

  1. Módulos Core (Nativos)
  2. Módulos Locales (Tu código)
  3. Módulos de Terceros (node_modules)
// 1. Módulos Core - Sin ./ y sin instalar
const fs = require('fs');
const http = require('http');
const path = require('path');

// 2. Módulos Locales - Con ./ o ruta relativa
const miModulo = require('./mi-modulo');
const utilidades = require('../utils/helpers');

// 3. Módulos de Terceros - Sin ./ y desde node_modules
const express = require('express');
const lodash = require('lodash');
Enter fullscreen mode Exit fullscreen mode

CommonJS vs ES Modules

Node.js soporta dos sistemas de módulos: CommonJS (el original) y ES Modules (el estándar de JavaScript moderno).

CommonJS (CJS)

El sistema de módulos original de Node.js. Usa require() y module.exports.

// math.js - Exportando con CommonJS
function sumar(a, b) {
  return a + b;
}

function restar(a, b) {
  return a - b;
}

function multiplicar(a, b) {
  return a * b;
}

// Forma 1: Exportar múltiples funciones
module.exports = {
  sumar,
  restar,
  multiplicar
};

// Forma 2: Exportar una por una
module.exports.sumar = sumar;
module.exports.restar = restar;

// Forma 3: Usando exports (atajo de module.exports)
exports.sumar = sumar;
exports.restar = restar;

// ⚠️ CUIDADO: Esto NO funciona
exports = { sumar, restar }; // Rompe la referencia
Enter fullscreen mode Exit fullscreen mode
// app.js - Importando con CommonJS
const math = require('./math');

console.log(math.sumar(5, 3));     // 8
console.log(math.restar(10, 4));   // 6

// Desestructuración
const { sumar, multiplicar } = require('./math');
console.log(sumar(2, 3));          // 5
Enter fullscreen mode Exit fullscreen mode

Exportación por Defecto en CommonJS

// logger.js
class Logger {
  log(message) {
    console.log(`[LOG]: ${message}`);
  }

  error(message) {
    console.error(`[ERROR]: ${message}`);
  }
}

// Exportar una sola cosa (similar a export default)
module.exports = Logger;

// O crear instancia directamente
module.exports = new Logger();
Enter fullscreen mode Exit fullscreen mode
// app.js
const Logger = require('./logger');
const logger = new Logger();
logger.log('Aplicación iniciada');

// O si exportaste la instancia
const logger = require('./logger');
logger.log('Aplicación iniciada');
Enter fullscreen mode Exit fullscreen mode

ES Modules (ESM)

El estándar moderno de JavaScript. Usa import y export.

// math.mjs - Exportando con ES Modules
export function sumar(a, b) {
  return a + b;
}

export function restar(a, b) {
  return a - b;
}

export function multiplicar(a, b) {
  return a * b;
}

// Export default (solo uno por archivo)
export default function dividir(a, b) {
  if (b === 0) throw new Error('División por cero');
  return a / b;
}
Enter fullscreen mode Exit fullscreen mode
// app.mjs - Importando con ES Modules
import dividir, { sumar, restar } from './math.mjs';

console.log(sumar(5, 3));      // 8
console.log(dividir(10, 2));   // 5

// Importar todo
import * as math from './math.mjs';
console.log(math.sumar(5, 3)); // 8

// Renombrar imports
import { sumar as add, restar as subtract } from './math.mjs';
console.log(add(5, 3));        // 8
Enter fullscreen mode Exit fullscreen mode

Habilitar ES Modules en Node.js

Opción 1: Usar extensión .mjs

// math.mjs
export const PI = 3.14159;
Enter fullscreen mode Exit fullscreen mode

Opción 2: Configurar package.json

{
  "type": "module"
}
Enter fullscreen mode Exit fullscreen mode

Ahora todos los archivos .js se tratan como ES Modules. Para usar CommonJS en este caso, usa .cjs:

// legacy.cjs
module.exports = { algo: 'valor' };
Enter fullscreen mode Exit fullscreen mode

Diferencias Clave: CommonJS vs ES Modules

Característica CommonJS ES Modules
Sintaxis require()/module.exports import/export
Carga Síncrona Asíncrona
Momento Runtime (durante ejecución) Parse time (antes de ejecutar)
Dinámico require(variable) ❌ Rutas estáticas
Tree Shaking
Top-level await
__dirname ❌ (necesita workaround)
Compatibilidad Node.js desde siempre Node.js 12+ estable

Carga Dinámica en ESM

// CommonJS - require dinámico
const moduleName = 'express';
const modulo = require(moduleName); // ✅ Funciona

// ES Modules - import dinámico
const moduleName = 'express';
const modulo = await import(moduleName); // ✅ Funciona (async)

// Ejemplo práctico
async function cargarModulo(nombre) {
  try {
    const modulo = await import(`./${nombre}.mjs`);
    return modulo;
  } catch (error) {
    console.error(`No se pudo cargar ${nombre}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

__dirname y __filename en ES Modules

// CommonJS - Disponible automáticamente
console.log(__dirname);  // /ruta/al/directorio
console.log(__filename); // /ruta/al/archivo.js

// ES Modules - Necesitas reconstruirlo
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

console.log(__dirname);  // /ruta/al/directorio
console.log(__filename); // /ruta/al/archivo.mjs
Enter fullscreen mode Exit fullscreen mode

Interoperabilidad: Mezclar CJS y ESM

// En ES Modules, puedes importar CommonJS
import express from 'express'; // express usa CommonJS
import { Router } from 'express';

// En CommonJS, NO puedes hacer require de ES Modules
// Necesitas import() dinámico
async function cargarESM() {
  const modulo = await import('./mi-modulo.mjs');
  modulo.hacerAlgo();
}
Enter fullscreen mode Exit fullscreen mode

¿Cuál Usar?

Usa CommonJS si:

  • Trabajas con código legacy
  • Necesitas compatibilidad con Node.js antiguo
  • Necesitas require() dinámico frecuentemente
  • La mayoría de tus dependencias son CommonJS

Usa ES Modules si:

  • Proyecto nuevo
  • Quieres el estándar moderno de JavaScript
  • Necesitas Tree Shaking (eliminar código no usado)
  • Quieres usar Top-level await
  • Compartes código con el frontend

Recomendación actual (2025): ES Modules para proyectos nuevos, CommonJS sigue siendo perfectamente válido.


Module Caching

Node.js cachea los módulos después de la primera carga. Esto significa que require() o import del mismo módulo retorna la misma instancia.

Cómo Funciona el Caché

// counter.js
let count = 0;

function increment() {
  count++;
  return count;
}

function getCount() {
  return count;
}

module.exports = { increment, getCount };
Enter fullscreen mode Exit fullscreen mode
// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');

console.log(counter1.increment()); // 1
console.log(counter2.increment()); // 2 (¡misma instancia!)

console.log(counter1 === counter2); // true

console.log(counter1.getCount());  // 2
console.log(counter2.getCount());  // 2
Enter fullscreen mode Exit fullscreen mode

Explicación: Node.js cargó counter.js una sola vez. Ambas variables apuntan al mismo objeto.

Ubicación del Caché

// Ver módulos cacheados
console.log(require.cache);

// Salida:
// {
//   '/ruta/completa/app.js': Module { ... },
//   '/ruta/completa/counter.js': Module { ... },
//   ...
// }
Enter fullscreen mode Exit fullscreen mode

Limpiar el Caché (Casos Especiales)

// counter.js
let count = 0;
module.exports = {
  increment: () => ++count,
  getCount: () => count
};
Enter fullscreen mode Exit fullscreen mode
// app.js
const counter1 = require('./counter');
console.log(counter1.increment()); // 1

// Limpiar el caché
const counterPath = require.resolve('./counter');
delete require.cache[counterPath];

// Ahora se carga de nuevo
const counter2 = require('./counter');
console.log(counter2.increment()); // 1 (nueva instancia)

console.log(counter1 === counter2); // false
Enter fullscreen mode Exit fullscreen mode

⚠️ Advertencia: Limpiar el caché manualmente es raro y puede causar problemas. Úsalo solo si realmente lo necesitas (ej: hot reloading en desarrollo).

Patrón Singleton con Módulos

El caché de módulos hace que sea trivial implementar Singletons:

// database.js
class Database {
  constructor() {
    console.log('Conectando a la base de datos...');
    this.connection = this.connect();
  }

  connect() {
    // Simulación de conexión
    return { connected: true };
  }

  query(sql) {
    console.log(`Ejecutando: ${sql}`);
  }
}

// Exportar una instancia única
module.exports = new Database();
Enter fullscreen mode Exit fullscreen mode
// app.js
const db1 = require('./database'); // "Conectando a la base de datos..."
const db2 = require('./database'); // (no imprime nada)

console.log(db1 === db2); // true - misma instancia

db1.query('SELECT * FROM users');
db2.query('SELECT * FROM posts'); // usa la misma conexión
Enter fullscreen mode Exit fullscreen mode

Comparación con Python

# Python también cachea módulos
import sys

# Primera importación
import mi_modulo  # Código se ejecuta

# Segunda importación
import mi_modulo  # Usa la versión cacheada

# Ver módulos cacheados
print(sys.modules)

# Recargar un módulo
import importlib
importlib.reload(mi_modulo)
Enter fullscreen mode Exit fullscreen mode

El Archivo package.json

El package.json es el corazón de cualquier proyecto Node.js. Define metadatos, dependencias y scripts.

Crear un package.json

# Interactivo
npm init

# Con valores por defecto
npm init -y
Enter fullscreen mode Exit fullscreen mode

Estructura Completa

{
  "name": "mi-proyecto",
  "version": "1.0.0",
  "description": "Descripción de mi proyecto",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "jest",
    "build": "webpack --mode production",
    "lint": "eslint .",
    "format": "prettier --write ."
  },
  "keywords": ["api", "backend", "nodejs"],
  "author": "Tu Nombre <email@example.com>",
  "license": "MIT",
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=9.0.0"
  },
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.0",
    "jest": "^29.5.0",
    "eslint": "^8.40.0"
  },
  "optionalDependencies": {
    "sharp": "^0.32.0"
  },
  "peerDependencies": {
    "react": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Campos Importantes

1. name y version

{
  "name": "mi-paquete",
  "version": "1.2.3"
}
Enter fullscreen mode Exit fullscreen mode

Reglas para name:

  • Lowercase, sin espacios
  • Puede tener guiones y underscores
  • Debe ser único en npm (si vas a publicar)

Scoped packages:

{
  "name": "@mi-usuario/mi-paquete"
}
Enter fullscreen mode Exit fullscreen mode

2. main y module

{
  "main": "index.js",      // Punto de entrada CommonJS
  "module": "index.mjs",   // Punto de entrada ES Modules
  "exports": {
    ".": {
      "import": "./index.mjs",
      "require": "./index.js"
    },
    "./utils": {
      "import": "./utils/index.mjs",
      "require": "./utils/index.js"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. scripts

Los scripts más comunes:

{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "build": "tsc",
    "lint": "eslint src/**/*.js",
    "format": "prettier --write src/**/*.js",
    "prepare": "husky install",
    "prestart": "npm run build",
    "postinstall": "echo 'Instalación completada'"
  }
}
Enter fullscreen mode Exit fullscreen mode

Ejecutar scripts:

npm run dev
npm test  # Atajo para npm run test
npm start # Atajo para npm run start

# Con argumentos
npm run test -- --watch
npm run lint -- --fix
Enter fullscreen mode Exit fullscreen mode

Pre y Post Hooks:

{
  "scripts": {
    "pretest": "npm run lint",    // Se ejecuta ANTES de test
    "test": "jest",
    "posttest": "npm run coverage" // Se ejecuta DESPUÉS de test
  }
}
Enter fullscreen mode Exit fullscreen mode

4. dependencies vs devDependencies

{
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.0.0",
    "dotenv": "^16.0.3"
  },
  "devDependencies": {
    "nodemon": "^3.0.0",
    "jest": "^29.5.0",
    "eslint": "^8.40.0",
    "typescript": "^5.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

dependencies:

  • Necesarios para ejecutar la aplicación en producción
  • Se instalan con npm install
  • Se incluyen cuando alguien instala tu paquete

devDependencies:

  • Solo necesarios durante el desarrollo
  • Herramientas de testing, linting, bundling
  • NO se instalan cuando alguien usa tu paquete
  • Se instalan con npm install (en tu proyecto)
  • Se omiten con npm install --production
# Instalar como dependencia
npm install express

# Instalar como devDependency
npm install -D jest

# Instalar solo dependencias de producción
npm install --production
Enter fullscreen mode Exit fullscreen mode

5. engines

Especifica versiones de Node.js y npm requeridas:

{
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=9.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Para forzar estas versiones:

{
  "engines": {
    "node": "18.x"
  },
  "engineStrict": true
}
Enter fullscreen mode Exit fullscreen mode

O usa .nvmrc:

18.16.0
Enter fullscreen mode Exit fullscreen mode
nvm use
Enter fullscreen mode Exit fullscreen mode

6. optionalDependencies

Dependencias que pueden fallar al instalar sin romper todo:

{
  "optionalDependencies": {
    "sharp": "^0.32.0",  // Procesamiento de imágenes (dependencias nativas)
    "fsevents": "^2.3.2" // Solo necesario en macOS
  }
}
Enter fullscreen mode Exit fullscreen mode
// Usar dependencias opcionales de forma segura
let sharp;
try {
  sharp = require('sharp');
} catch (error) {
  console.warn('sharp no disponible, usando alternativa');
  sharp = require('./fallback-image-processor');
}
Enter fullscreen mode Exit fullscreen mode

7. peerDependencies

Para librerías que necesitan que el usuario instale otra dependencia:

{
  "name": "mi-plugin-react",
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "peerDependenciesMeta": {
    "react-dom": {
      "optional": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Esto dice: "Mi plugin funciona con React 18, pero el usuario debe instalarlo".


Gestores de Paquetes

npm (Node Package Manager)

El gestor de paquetes que viene con Node.js.

# Instalar dependencias
npm install              # Instala todo de package.json
npm install express      # Instala express y lo agrega
npm install -D jest      # Instala como devDependency
npm install express@4.17.1  # Versión específica

# Desinstalar
npm uninstall express

# Actualizar
npm update               # Actualiza todas las dependencias
npm update express       # Actualiza solo express
npm outdated            # Ver paquetes desactualizados

# Listar paquetes
npm list                # Árbol de dependencias
npm list --depth=0      # Solo dependencias directas

# Ejecutar scripts
npm run dev
npm start
npm test

# Limpiar caché
npm cache clean --force

# Ver información de un paquete
npm info express
npm view express versions  # Ver todas las versiones disponibles
Enter fullscreen mode Exit fullscreen mode

Yarn

Alternativa a npm desarrollada por Facebook.

# Instalar Yarn
npm install -g yarn

# Comandos equivalentes
yarn                    # = npm install
yarn add express        # = npm install express
yarn add -D jest        # = npm install -D jest
yarn remove express     # = npm uninstall express
yarn upgrade            # = npm update

# Yarn específico
yarn why express        # Por qué está instalado express
yarn upgrade-interactive  # Actualizar interactivamente
Enter fullscreen mode Exit fullscreen mode

Ventajas de Yarn:

  • Más rápido (caché mejor optimizado)
  • yarn.lock más determinista
  • Workspaces para monorepos
  • Plugin system

pnpm (Performant npm)

El gestor más eficiente en espacio en disco.

# Instalar pnpm
npm install -g pnpm

# Comandos equivalentes
pnpm install            # = npm install
pnpm add express        # = npm install express
pnpm add -D jest        # = npm install -D jest
pnpm remove express     # = npm uninstall express
pnpm update             # = npm update

# pnpm específico
pnpm store status       # Ver estado del store
pnpm store prune        # Limpiar store
Enter fullscreen mode Exit fullscreen mode

Ventajas de pnpm:

  • Ahorra espacio: Usa hard links a un store central
  • Más rápido: Instalaciones paralelas eficientes
  • Más seguro: Estructura de node_modules más estricta
  • Monorepos: Excelente soporte para workspaces

Comparación

Proyecto A: express, lodash
Proyecto B: express, axios
Proyecto C: lodash, axios

┌─────────── npm/Yarn ──────────────┐
│ Proyecto A/node_modules/          │
│   ├── express/                    │
│   └── lodash/                     │
│                                   │
│ Proyecto B/node_modules/          │
│   ├── express/ (duplicado)        │
│   └── axios/                      │
│                                   │
│ Proyecto C/node_modules/          │
│   ├── lodash/ (duplicado)         │
│   └── axios/ (duplicado)          │
└───────────────────────────────────┘

┌─────────── pnpm ──────────────────┐
│ ~/.pnpm-store/                    │
│   ├── express@4.18.2/             │
│   ├── lodash@4.17.21/             │
│   └── axios@1.4.0/                │
│                                   │
│ Proyecto A/node_modules/ (links)  │
│ Proyecto B/node_modules/ (links)  │
│ Proyecto C/node_modules/ (links)  │
└───────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

¿Cuál Usar?

Gestor Usar si...
npm Proyecto simple, compatibilidad máxima
Yarn Equipo grande, necesitas workspaces, velocidad
pnpm Múltiples proyectos, quieres ahorrar espacio, monorepos

node_modules y Resolución de Dependencias

Estructura de node_modules

mi-proyecto/
├── node_modules/
│   ├── express/
│   │   ├── package.json
│   │   ├── index.js
│   │   └── node_modules/        <- Dependencias de express
│   │       ├── body-parser/
│   │       └── cookie-parser/
│   ├── lodash/
│   └── axios/
├── package.json
└── index.js
Enter fullscreen mode Exit fullscreen mode

Algoritmo de Resolución de Módulos

Cuando haces require('express'), Node.js busca en este orden:

1. Core modules (módulos nativos)
   - Si 'express' fuera un módulo core, se usa directamente

2. node_modules en el directorio actual
   /home/user/proyecto/node_modules/express

3. node_modules en el directorio padre
   /home/user/node_modules/express

4. Continúa subiendo hasta la raíz
   /home/node_modules/express
   /node_modules/express

5. NODE_PATH (ruta de entorno)

6. Error: Cannot find module 'express'
Enter fullscreen mode Exit fullscreen mode

Ejemplo visual:

/home/user/proyecto/src/controllers/user.js
                          ↓
         require('express')
                          ↓
    ¿Existe express core? NO
                          ↓
    /home/user/proyecto/src/controllers/node_modules/express? NO
                          ↓
    /home/user/proyecto/src/node_modules/express? NO
                          ↓
    /home/user/proyecto/node_modules/express? ✅ SÍ - USAR ESTE
Enter fullscreen mode Exit fullscreen mode

Hoisting de Dependencias

npm y Yarn "aplanan" las dependencias cuando es posible:

Sin hoisting:

node_modules/
├── express/
│   └── node_modules/
│       ├── body-parser/
│       └── cookie-parser/
└── body-parser/  <- También lo usa otro paquete
Enter fullscreen mode Exit fullscreen mode

Con hoisting:

node_modules/
├── express/
├── body-parser/      <- Compartido (subió al nivel superior)
└── cookie-parser/    <- Compartido
Enter fullscreen mode Exit fullscreen mode

Conflictos de Versiones

Proyecto necesita:
- paquete-a@1.0.0 (requiere lodash@4.17.0)
- paquete-b@2.0.0 (requiere lodash@4.17.21)
Enter fullscreen mode Exit fullscreen mode

Resultado:

node_modules/
├── lodash@4.17.21/          <- Versión más reciente en raíz
├── paquete-a/
│   └── node_modules/
│       └── lodash@4.17.0/   <- Versión específica anidada
└── paquete-b/               <- Usa lodash de raíz
Enter fullscreen mode Exit fullscreen mode

require.resolve()

Útil para encontrar la ruta exacta de un módulo:

// Encontrar dónde está express
const expressPath = require.resolve('express');
console.log(expressPath);
// /home/user/proyecto/node_modules/express/index.js

// Encontrar un archivo específico dentro de un paquete
const bodyParserPath = require.resolve('body-parser/lib/types/json');
console.log(bodyParserPath);

// Útil para debugging
try {
  require.resolve('paquete-inexistente');
} catch (error) {
  console.error('El paquete no está instalado');
}
Enter fullscreen mode Exit fullscreen mode

Semantic Versioning

npm usa Semantic Versioning (SemVer) para las versiones de paquetes.

Formato: MAJOR.MINOR.PATCH

     5  .  2  .  10
     │     │     │
   MAJOR MINOR PATCH
Enter fullscreen mode Exit fullscreen mode

Reglas:

  • MAJOR (5): Cambios incompatibles (breaking changes)
  • MINOR (2): Nueva funcionalidad compatible hacia atrás
  • PATCH (10): Corrección de bugs compatible

Rangos de Versiones

{
  "dependencies": {
    "express": "4.18.2",       // Versión exacta
    "lodash": "^4.17.21",      // Compatible (recomendado)
    "axios": "~1.4.0",         // Solo patches
    "mongoose": "*",           // Cualquier versión (¡peligroso!)
    "dotenv": ">=16.0.0",      // Mayor o igual
    "cors": "<3.0.0",          // Menor que
    "helmet": "1.x",           // Cualquier 1.x.x
    "morgan": "1.2.x"          // Cualquier 1.2.x
  }
}
Enter fullscreen mode Exit fullscreen mode

Operador Caret (^) - Recomendado

Permite cambios que no modifican el primer número diferente de cero.

^4.18.2  permite:  4.18.2 a < 5.0.0
^0.2.3   permite:  0.2.3 a < 0.3.0  (más restrictivo con 0.x)
^0.0.4   permite:  solo 0.0.4       (muy restrictivo con 0.0.x)
Enter fullscreen mode Exit fullscreen mode

Ejemplos:

{
  "express": "^4.18.2"
}
Enter fullscreen mode Exit fullscreen mode

Instalará:

  • ✅ 4.18.3 (patch)
  • ✅ 4.19.0 (minor)
  • ✅ 4.20.5 (minor + patch)
  • ❌ 5.0.0 (major)

Operador Tilde (~)

Permite solo cambios de PATCH.

~4.18.2  permite:  4.18.2 a < 4.19.0
~0.2.3   permite:  0.2.3 a < 0.3.0
Enter fullscreen mode Exit fullscreen mode

Ejemplos:

{
  "axios": "~1.4.0"
}
Enter fullscreen mode Exit fullscreen mode

Instalará:

  • ✅ 1.4.1 (patch)
  • ✅ 1.4.9 (patch)
  • ❌ 1.5.0 (minor)
  • ❌ 2.0.0 (major)

Pre-releases

1.0.0-alpha.1
1.0.0-beta.2
1.0.0-rc.1     (release candidate)
1.0.0          (stable)
Enter fullscreen mode Exit fullscreen mode
# Instalar pre-release
npm install express@next
npm install lodash@5.0.0-beta.1
Enter fullscreen mode Exit fullscreen mode

Comparación Visual

// package.json
{
  "lodash": "^4.17.21",
  "express": "~4.18.0",
  "mongoose": "7.0.0"
}

// npm install instalará:
{
  "lodash": "4.17.21",     // La última 4.x.x
  "express": "4.18.2",     // La última 4.18.x
  "mongoose": "7.0.0"      // Exactamente 7.0.0
}
Enter fullscreen mode Exit fullscreen mode

Herramientas para Gestionar Versiones

# Ver versiones disponibles
npm view lodash versions --json

# Ver información del paquete
npm info lodash

# Ver dependencias desactualizadas
npm outdated

# Actualizar respetando rangos de package.json
npm update

# Instalar la última versión (ignora rangos)
npm install lodash@latest
Enter fullscreen mode Exit fullscreen mode

Lock Files

Los lock files garantizan que todos los desarrolladores y el entorno de producción tengan exactamente las mismas versiones de dependencias.

package-lock.json (npm)

Se genera automáticamente cuando ejecutas npm install.

{
  "name": "mi-proyecto",
  "version": "1.0.0",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "": {
      "name": "mi-proyecto",
      "version": "1.0.0",
      "dependencies": {
        "express": "^4.18.2"
      }
    },
    "node_modules/express": {
      "version": "4.18.2",
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
      "dependencies": {
        "accepts": "~1.3.8",
        "array-flatten": "1.1.1"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Características:

  • Especifica versiones exactas
  • Incluye checksums de integridad
  • Se debe versionar en git
  • Garantiza instalaciones reproducibles

yarn.lock (Yarn)

Similar pero con formato diferente:

express@^4.18.2:
  version "4.18.2"
  resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe4f3f01554532c3aa1d3a9b5c5c2c3e8d1f5"
  integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
  dependencies:
    accepts "~1.3.8"
    array-flatten "1.1.1"
Enter fullscreen mode Exit fullscreen mode

pnpm-lock.yaml (pnpm)

lockfileVersion: 5.4

specifiers:
  express: ^4.18.2

dependencies:
  express: 4.18.2

packages:
  /express/4.18.2:
    resolution: {integrity: sha512-5/PsL6iGPdfQ/...}
    dependencies:
      accepts: 1.3.8
      array-flatten: 1.1.1
Enter fullscreen mode Exit fullscreen mode

Buenas Prácticas con Lock Files

# ✅ SIEMPRE versiona el lock file
git add package-lock.json
git commit -m "Add package-lock.json"

# ✅ Usa npm ci en producción (más rápido y confiable)
npm ci  # En lugar de npm install

# ✅ Regenerar lock file si hay conflictos
rm package-lock.json node_modules
npm install

# ❌ NO edites el lock file manualmente
# ❌ NO agregues lock files al .gitignore
Enter fullscreen mode Exit fullscreen mode

Seguridad y Auditorías

npm audit

npm incluye herramientas de seguridad integradas:

# Revisar vulnerabilidades
npm audit

# Salida de ejemplo:
# ┌───────────────┬──────────────────────────────────────────────────────────────┐
# │ High          │ Regular Expression Denial of Service                        │
# ├───────────────┼──────────────────────────────────────────────────────────────┤
# │ Package       │ semver                                                       │
# ├───────────────┼──────────────────────────────────────────────────────────────┤
# │ Patched in    │ >=7.5.2                                                      │
# ├───────────────┼──────────────────────────────────────────────────────────────┤
# │ Dependency of │ @angular/cli                                                 │
# └───────────────┴──────────────────────────────────────────────────────────────┘

# Intentar arreglar automáticamente
npm audit fix

# Arreglar incluso con breaking changes
npm audit fix --force

# Ver detalles en formato JSON
npm audit --json
Enter fullscreen mode Exit fullscreen mode

Niveles de Severidad

  • Critical: Explotable remotamente, requiere acción inmediata
  • High: Difícil de explotar pero serio
  • Moderate: Requiere condiciones específicas
  • Low: Riesgo mínimo

Herramientas Adicionales de Seguridad

# Snyk - Herramienta comercial con plan gratuito
npm install -g snyk
snyk auth
snyk test
snyk monitor

# Retire.js - Detecta dependencias conocidas como vulnerables
npm install -g retire
retire

# audit-ci - Para pipelines de CI/CD
npm install -g audit-ci
audit-ci --moderate
Enter fullscreen mode Exit fullscreen mode

Ejemplo de Política de Seguridad

// .auditrc
{
  "low": false,
  "moderate": true,
  "high": true,
  "critical": true,
  "report-type": "full"
}
Enter fullscreen mode Exit fullscreen mode

Buenas Prácticas

Estructura de Proyecto

mi-proyecto/
├── package.json
├── package-lock.json
├── .nvmrc                    # Versión de Node.js
├── .gitignore
├── README.md
├── src/
│   ├── index.js
│   ├── routes/
│   ├── models/
│   └── utils/
├── tests/
├── docs/
└── scripts/
    ├── build.js
    └── deploy.js
Enter fullscreen mode Exit fullscreen mode

package.json Optimizado

{
  "name": "mi-api",
  "version": "1.0.0",
  "description": "API REST para gestión de usuarios",
  "main": "src/index.js",
  "type": "module",
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=9.0.0"
  },
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "lint": "eslint src/**/*.js",
    "lint:fix": "eslint src/**/*.js --fix",
    "format": "prettier --write src/**/*.js",
    "build": "node scripts/build.js",
    "prestart": "npm run lint",
    "postinstall": "npm audit",
    "security": "npm audit && snyk test"
  },
  "keywords": ["api", "rest", "nodejs", "express"],
  "author": "Tu Nombre <email@example.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/usuario/mi-api.git"
  },
  "bugs": {
    "url": "https://github.com/usuario/mi-api/issues"
  },
  "homepage": "https://github.com/usuario/mi-api#readme",
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.0.0",
    "dotenv": "^16.0.3",
    "helmet": "^6.1.5",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^2.0.22",
    "jest": "^29.5.0",
    "supertest": "^6.3.3",
    "eslint": "^8.40.0",
    "prettier": "^2.8.8",
    "@types/node": "^18.16.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

.gitignore Completo

# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Coverage directory used by tools like istanbul
coverage/
*.lcov

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Build outputs
dist/
build/
*.tgz

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
Enter fullscreen mode Exit fullscreen mode

Scripts Útiles

{
  "scripts": {
    "clean": "rm -rf node_modules package-lock.json && npm install",
    "outdated": "npm outdated",
    "update:patch": "npm update",
    "update:minor": "npx npm-check-updates -u -t minor",
    "update:major": "npx npm-check-updates -u",
    "size": "npm run build && bundlesize",
    "deps:check": "depcheck",
    "deps:unused": "depcheck --unused",
    "release": "standard-version",
    "docker:build": "docker build -t mi-app .",
    "docker:run": "docker run -p 3000:3000 mi-app"
  }
}
Enter fullscreen mode Exit fullscreen mode

Herramientas de Desarrollo

# Analizar dependencias no utilizadas
npm install -g depcheck
depcheck

# Actualizar dependencias interactivamente
npm install -g npm-check-updates
ncu -i

# Ver tamaño de dependencias
npm install -g bundlesize
bundlesize

# Analizar el bundle
npm install -g webpack-bundle-analyzer
Enter fullscreen mode Exit fullscreen mode

Variables de Entorno

// .env
NODE_ENV=development
PORT=3000
DB_URL=mongodb://localhost:27017/mi-app
JWT_SECRET=mi-secreto-super-seguro
LOG_LEVEL=debug

// src/config.js
import dotenv from 'dotenv';
dotenv.config();

export const config = {
  port: process.env.PORT || 3000,
  dbUrl: process.env.DB_URL,
  jwtSecret: process.env.JWT_SECRET,
  nodeEnv: process.env.NODE_ENV || 'development',
  logLevel: process.env.LOG_LEVEL || 'info'
};

// Validación de variables requeridas
const requiredVars = ['DB_URL', 'JWT_SECRET'];
for (const envVar of requiredVars) {
  if (!process.env[envVar]) {
    console.error(`Error: Variable de entorno ${envVar} es requerida`);
    process.exit(1);
  }
}
Enter fullscreen mode Exit fullscreen mode

Cuestionario de Entrevista

Preguntas Fundamentales

1. ¿Cuál es la diferencia entre CommonJS y ES Modules?

Respuesta:

  • CommonJS: Sistema de módulos original de Node.js, usa require()/module.exports, carga síncrona
  • ES Modules: Estándar moderno de JavaScript, usa import/export, carga asíncrona
  • CommonJS se carga en runtime, ESM se analiza en parse-time
  • ESM permite tree-shaking, CommonJS no

2. ¿Cómo funciona el module caching en Node.js?

Respuesta:

  • Node.js cachea módulos después de la primera carga
  • require() del mismo módulo retorna la misma instancia
  • El caché se almacena en require.cache
  • Permite implementar singletons fácilmente
  • Se puede limpiar manualmente con delete require.cache[path]

3. ¿Qué es la diferencia entre dependencies y devDependencies?

Respuesta:

  • dependencies: Necesarias para ejecutar la aplicación en producción
  • devDependencies: Solo necesarias durante el desarrollo (testing, linting, bundling)
  • npm install --production omite devDependencies
  • Cuando alguien instala tu paquete como dependencia, solo se instalan las dependencies

4. ¿Qué significa ^4.18.2 en package.json?

Respuesta:

  • Operador caret permite actualizaciones compatibles
  • Permite versiones desde 4.18.2 hasta < 5.0.0
  • Solo permite cambios de MINOR y PATCH, no MAJOR
  • Sigue Semantic Versioning (MAJOR.MINOR.PATCH)

5. ¿Para qué sirve package-lock.json?

Respuesta:

  • Garantiza instalaciones reproducibles con versiones exactas
  • Incluye checksums de integridad para seguridad
  • Debe versionarse en git
  • npm ci usa el lock file para instalaciones más rápidas y confiables

Preguntas Avanzadas

6. ¿Cómo resuelve Node.js las dependencias cuando haces require('express')?

Respuesta:

  1. Verifica si es un módulo core
  2. Busca en node_modules del directorio actual
  3. Busca en node_modules de directorios padre
  4. Continúa hasta la raíz del sistema
  5. Verifica NODE_PATH
  6. Error si no encuentra

7. ¿Qué es hoisting de dependencias?

Respuesta:

  • npm y Yarn "aplanan" dependencias cuando es posible
  • Dependencias compartidas se mueven al nivel superior
  • Reduce duplicación y tamaño de node_modules
  • Puede causar problemas si el código depende de dependencias transitivas

8. ¿Cuál es la diferencia entre npm, Yarn y pnpm?

Respuesta:

  • npm: Gestor por defecto, compatible con todo
  • Yarn: Más rápido, mejor para monorepos, yarn.lock determinista
  • pnpm: Más eficiente en espacio (hard links), estructura más estricta

9. ¿Cómo manejarías una vulnerabilidad de seguridad en una dependencia?

Respuesta:

  1. npm audit para identificar vulnerabilidades
  2. npm audit fix para arreglos automáticos
  3. Actualizar manualmente si es necesario
  4. Considerar dependencias alternativas
  5. Implementar políticas de seguridad en CI/CD

10. ¿Qué es un peer dependency?

Respuesta:

  • Dependencia que el usuario debe instalar
  • Común en plugins y librerías
  • Evita conflictos de versiones
  • Permite compartir dependencias entre paquetes
  • Se define en peerDependencies del package.json

Ejercicios Prácticos

Ejercicio 1: Crear un módulo que exporte múltiples funciones usando CommonJS y ES Modules.

Ejercicio 2: Implementar un sistema de caché personalizado aprovechando el module caching.

Ejercicio 3: Configurar un proyecto con scripts de npm para desarrollo, testing y producción.

Ejercicio 4: Resolver conflictos de versiones en un proyecto con múltiples dependencias.

Ejercicio 5: Implementar un workflow de CI/CD que incluya auditoría de seguridad.


Esta guía completa cubre todos los aspectos esenciales del sistema de módulos y gestión de dependencias en Node.js, desde conceptos básicos hasta técnicas avanzadas y mejores prácticas profesionales.

Top comments (0)