Guía Completa: Módulos y Gestión de Dependencias en Node.js
Tabla de Contenidos
- Sistema de Módulos en Node.js
- CommonJS vs ES Modules
- Module Caching
- El Archivo package.json
- Gestores de Paquetes
- node_modules y Resolución de Dependencias
- Semantic Versioning
- Lock Files
- Seguridad y Auditorías
- Buenas Prácticas
- 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))
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)
Tipos de Módulos en Node.js
- Módulos Core (Nativos)
- Módulos Locales (Tu código)
- 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');
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
// 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
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();
// 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');
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;
}
// 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
Habilitar ES Modules en Node.js
Opción 1: Usar extensión .mjs
// math.mjs
export const PI = 3.14159;
Opción 2: Configurar package.json
{
"type": "module"
}
Ahora todos los archivos .js
se tratan como ES Modules. Para usar CommonJS en este caso, usa .cjs
:
// legacy.cjs
module.exports = { algo: 'valor' };
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}`);
}
}
__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
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();
}
¿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 };
// 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
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 { ... },
// ...
// }
Limpiar el Caché (Casos Especiales)
// counter.js
let count = 0;
module.exports = {
increment: () => ++count,
getCount: () => count
};
// 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
⚠️ 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();
// 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
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)
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
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"
}
}
Campos Importantes
1. name y version
{
"name": "mi-paquete",
"version": "1.2.3"
}
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"
}
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"
}
}
}
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'"
}
}
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
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
}
}
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"
}
}
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
5. engines
Especifica versiones de Node.js y npm requeridas:
{
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}
Para forzar estas versiones:
{
"engines": {
"node": "18.x"
},
"engineStrict": true
}
O usa .nvmrc
:
18.16.0
nvm use
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
}
}
// 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');
}
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
}
}
}
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
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
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
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) │
└───────────────────────────────────┘
¿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
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'
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
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
Con hoisting:
node_modules/
├── express/
├── body-parser/ <- Compartido (subió al nivel superior)
└── cookie-parser/ <- Compartido
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)
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
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');
}
Semantic Versioning
npm usa Semantic Versioning (SemVer) para las versiones de paquetes.
Formato: MAJOR.MINOR.PATCH
5 . 2 . 10
│ │ │
MAJOR MINOR PATCH
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
}
}
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)
Ejemplos:
{
"express": "^4.18.2"
}
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
Ejemplos:
{
"axios": "~1.4.0"
}
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)
# Instalar pre-release
npm install express@next
npm install lodash@5.0.0-beta.1
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
}
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
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"
}
}
}
}
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"
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
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
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
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
Ejemplo de Política de Seguridad
// .auditrc
{
"low": false,
"moderate": true,
"high": true,
"critical": true,
"report-type": "full"
}
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
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"
}
}
.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
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"
}
}
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
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);
}
}
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:
- Verifica si es un módulo core
- Busca en node_modules del directorio actual
- Busca en node_modules de directorios padre
- Continúa hasta la raíz del sistema
- Verifica NODE_PATH
- 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:
-
npm audit
para identificar vulnerabilidades -
npm audit fix
para arreglos automáticos - Actualizar manualmente si es necesario
- Considerar dependencias alternativas
- 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)