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 protected]>",
"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 [email protected] # 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.lockmá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/ │
│ ├── [email protected]/ │
│ ├── [email protected]/ │
│ └── [email protected]/ │
│ │
│ 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:
- [email protected] (requiere [email protected])
- [email protected] (requiere [email protected])
Resultado:
node_modules/
├── [email protected]/ <- Versión más reciente en raíz
├── paquete-a/
│ └── node_modules/
│ └── [email protected]/ <- 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)
Comparación Visual
javascript
// package.json
{
"lodash": "^
SOCIAL SHARE CARD GENERATOR