En las dos lecciones anteriores hemos escrito rutas así: 'datos/eventos.json', `${DIRECTORIO_BASE}/${mes}`, ruta.slice(ruta.lastIndexOf('/') + 1). Todo eso funciona en tu máquina, hoy, ejecutando desde la raíz del proyecto. Y todo eso está mal.
Falla si alguien ejecuta el programa desde otra carpeta. Falla en Windows. Falla si un nombre trae un espacio en el sitio equivocado. Y, en el caso más grave, permite que un usuario malintencionado escape del directorio que creías estar sirviendo y se lleve ficheros del sistema.
El módulo path existe para eliminar toda esa clase de errores. No calcula nada del disco —path es puro tratamiento de texto y no toca el sistema de archivos—, pero conoce las reglas de cada plataforma. Al terminar esta lección tendrás las rutas de Escena Viva centralizadas en src/config/rutas.js, refactorizadas las dos lecciones anteriores, y sabrás blindar una ruta construida con datos que vienen de fuera.
Contenido
- Por qué concatenar rutas es un error
joinfrente aresolve: la diferencia exacta- Descomponer rutas:
basename,dirname,extname,parse,format process.cwd()frente a__dirname: el error clásicosep,posixywin32normalizeyrelative- Seguridad: recorrido de directorios
src/config/rutas.jsy la refactorización de Escena Viva
- Por qué concatenar rutas es un error
Pegar cadenas parece inofensivo hasta que dejas de controlar los trozos:
const directorio = 'informes/';
const nombre = '2026-08';
directorio + '/' + nombre; // 'informes//2026-08' <- barra duplicadaLos problemas concretos son cuatro y todos aparecen tarde:
| Problema | Ejemplo | Consecuencia |
|---|---|---|
| Separadores duplicados o ausentes | 'informes//2026-08', 'informesdatos' |
Funciona a veces; en comparaciones de texto, nunca |
| Separador equivocado | 'informes\\2026-08' en Linux |
El nombre de fichero contiene una barra invertida literal |
.. sin resolver |
'informes/2026-08/../2026-07' |
Dos rutas distintas apuntan al mismo sitio y no se detecta |
| Relativa contra absoluta | '/etc/passwd' pegado tras un prefijo |
La barra inicial reinicia la ruta y anula tu directorio base |
Ese último es la puerta del agujero de seguridad del apartado 7. La regla es sencilla y no admite excepciones: para construir una ruta, path.join o path.resolve; nunca el operador + ni una plantilla de texto.
const path = require('node:path');
path.join('informes/', '/2026-08', 'ocupacion.json');
// 'informes/2026-08/ocupacion.json' <- barras normalizadas
join frente a resolve: la diferencia exacta
join frente a resolve: la diferencia exactaLas dos combinan trozos de ruta, pero responden a preguntas distintas:
path.join(...)pega los segmentos y normaliza el resultado. Si el resultado es relativo, se queda relativo.path.resolve(...)construye siempre una ruta absoluta, procesando los argumentos de derecha a izquierda y parando en cuanto ha formado una ruta absoluta. Si se le acaban los argumentos sin conseguirlo, anteponeprocess.cwd().
Con el proceso ejecutándose en /home/ana/escena-viva:
| Llamada | path.join |
path.resolve |
|---|---|---|
('datos', 'eventos.json') |
datos/eventos.json |
/home/ana/escena-viva/datos/eventos.json |
('/datos', 'eventos.json') |
/datos/eventos.json |
/datos/eventos.json |
('datos', '/eventos.json') |
datos/eventos.json |
/eventos.json |
('informes', '..', 'datos') |
datos |
/home/ana/escena-viva/datos |
('informes', '../..', 'x') |
../x |
/home/ana/x |
() (sin argumentos) |
. |
/home/ana/escena-viva |
Las dos filas destacadas son las importantes. En la tercera, resolve descarta todo lo que había a la izquierda en cuanto encuentra un argumento absoluto: path.resolve('datos', '/eventos.json') es /eventos.json, no datos/eventos.json. join, en cambio, trata la barra inicial como un simple separador y la absorbe.
Y en la quinta fila, join conserva el .. que sobresale del punto de partida (no puede saber dónde está), mientras que resolve lo aplica sobre una ruta real y desaparece.
| Úsalo cuando... | Función |
|---|---|
| Compones segmentos que ya sabes relativos entre sí (nombre de fichero dentro de una carpeta) | join |
| Necesitas una ruta absoluta y definitiva (abrir un fichero, comparar rutas, validar) | resolve |
| Vas a comprobar que una ruta cae dentro de un directorio permitido | resolve, siempre |
- Descomponer rutas:
basename, dirname, extname, parse, format
basename, dirname, extname, parse, formatconst ruta = '/home/ana/escena-viva/informes/2026-08/ocupacion-2026-08-11.json';
path.basename(ruta); // 'ocupacion-2026-08-11.json'
path.basename(ruta, '.json'); // 'ocupacion-2026-08-11' <- sin extension
path.dirname(ruta); // '/home/ana/escena-viva/informes/2026-08'
path.extname(ruta); // '.json' (con el punto)path.parse devuelve todas las piezas a la vez, y path.format hace el viaje de vuelta:
const partes = path.parse(ruta);
// {
// root: '/',
// dir: '/home/ana/escena-viva/informes/2026-08',
// base: 'ocupacion-2026-08-11.json',
// ext: '.json',
// name: 'ocupacion-2026-08-11'
// }
// Cambiar la extension sin tocar texto a mano:
path.format({ ...partes, base: undefined, ext: '.csv' });
// '/home/ana/escena-viva/informes/2026-08/ocupacion-2026-08-11.csv'Ese base: undefined no es un capricho: format da prioridad a base sobre name + ext, así que si dejas el base original tu cambio de extensión se ignora en silencio. Es una de esas trampas que cuesta media hora localizar.
Dos avisos sobre extname: en un fichero sin punto devuelve la cadena vacía, y en archivo.tar.gz devuelve solo '.gz', porque no entiende de extensiones compuestas.
process.cwd() frente a __dirname: el error clásico
process.cwd() frente a __dirname: el error clásicoEste apartado explica el fallo más frecuente y más desconcertante del trabajo con ficheros en Node.
process.cwd() |
__dirname |
|
|---|---|---|
| Qué es | Directorio desde el que se lanzó el proceso | Directorio del fichero que contiene esa variable |
| Cambia si... | El usuario ejecuta desde otra carpeta | Nunca (salvo que muevas el fichero) |
A qué resuelve una ruta relativa de fs |
A esto | A nada: fs no lo usa |
Y aquí está la clave que lo explica todo: todas las rutas relativas que pasas a fs se resuelven contra process.cwd(), no contra el fichero donde escribes la línea. Nuestro src/catalogo-datos.js dice 'datos/eventos.json', y eso significa "datos/eventos.json a partir de donde el usuario haya lanzado el proceso".
# Desde la raiz del proyecto: funciona.
cd ~/escena-viva
node src/catalogo.js
# ...catalogo completo...
# Desde cualquier otro sitio: se rompe.
cd ~
node escena-viva/src/catalogo.js
# [catalogo] No se encuentra datos/eventos.jsonEl programa es el mismo, el fichero está en su sitio, y falla. Porque en el segundo caso process.cwd() es /home/ana y Node busca /home/ana/datos/eventos.json.
La consecuencia es más grave de lo que parece: el proyecto funcionará en tu terminal y fallará en el arranque automático del servidor, en el cron nocturno, en el contenedor Docker del Módulo 11 y en las pruebas del Módulo 9, porque en todos esos entornos el directorio de trabajo lo decide otro.
La solución es anclar las rutas al código, no al directorio de trabajo:
// MAL: depende de donde se ejecute el proceso.
const ruta = 'datos/eventos.json';
// BIEN: relativa al fichero, siempre la misma.
// __dirname es src/, asi que subimos un nivel hasta la raiz del proyecto.
const ruta = path.join(__dirname, '..', 'datos', 'eventos.json');Cuándo sí quieres process.cwd(): cuando la ruta la escribe el usuario en la línea de comandos (node herramienta.js informes/agosto.json), porque ahí lo relativo debe interpretarse desde donde está el usuario, no desde donde vive tu código. Esa es la regla completa: datos del proyecto contra __dirname; argumentos del usuario contra process.cwd().
sep, posix y win32
sep, posix y win32path.sep es el separador de la plataforma: '/' en Linux y macOS, '\\' en Windows. Es útil para partir una ruta en segmentos (ruta.split(path.sep)), pero no lo necesitas para construir: de eso ya se encarga join.
El módulo expone además dos variantes completas:
| Variante | Separador | Cuándo forzarla |
|---|---|---|
path.posix |
/ |
URLs, rutas dentro de un ZIP o un contenedor, claves de almacenamiento en la nube, rutas guardadas en base de datos |
path.win32 |
\ |
Manipular rutas de Windows desde otra plataforma (scripts de despliegue) |
path (por defecto) |
El de la plataforma | Todo lo que sea acceso real al disco |
La distinción importa más de lo que parece. Windows acepta / como separador al abrir ficheros, así que fs funciona igual; el problema aparece cuando esa ruta deja de ser una ruta y se convierte en un identificador: la URL de un cartel de evento, la clave de un objeto en un almacenamiento remoto, el nombre de una entrada dentro de un ZIP. Ahí informes\2026-08\cartel.png no es lo mismo que informes/2026-08/cartel.png, y nadie te avisa.
// Ruta real en disco: la de la plataforma.
const rutaFisica = path.join(DIRECTORIO_INFORMES, mes, fichero);
// Identificador que viajara en una URL: siempre POSIX.
const clavePublica = path.posix.join('informes', mes, fichero);En el Módulo 4, al servir archivos estáticos, esta distinción será obligatoria: las URLs usan / en todas las plataformas, sin excepción.
normalize y relative
normalize y relativepath.normalize('informes//2026-08/../2026-07/./ocupacion.json');
// 'informes/2026-07/ocupacion.json'normalize limpia una ruta: colapsa separadores repetidos, resuelve . y .. textualmente y corrige el separador. Lo que no hace es tocar el disco ni resolver enlaces simbólicos —para eso está fs.realpath—, y por eso un .. textual puede llevarte a un sitio distinto del real si hay enlaces por medio. join y resolve ya normalizan por dentro; normalize es para cuando recibes una ruta ya montada por otro.
relative responde a "¿cómo llego de aquí a allí?":
path.relative('/home/ana/escena-viva/src', '/home/ana/escena-viva/datos/eventos.json');
// '../datos/eventos.json'
path.relative('/home/ana/escena-viva', '/etc/passwd');
// '../../../etc/passwd' <- empieza por '..': el destino esta FUERAEse segundo ejemplo es la base de la comprobación de seguridad que viene ahora: si la ruta relativa de una base a un destino empieza por .., el destino está fuera de la base.
- Seguridad: recorrido de directorios
El recorrido de directorios (path traversal) es una de las vulnerabilidades más antiguas y más vivas de la web. Aparece siempre que se construye una ruta con datos que vienen de fuera.
Imagina el descargador de informes de Escena Viva: el usuario pide un fichero por su nombre y el servidor se lo devuelve.
// VULNERABLE. No hagas esto.
async function descargarInforme(nombrePedido) {
const ruta = path.join('informes', nombrePedido);
return fs.readFile(ruta, 'utf8');
}Con nombrePedido = '2026-08/ocupacion-2026-08-11.json' todo va bien. Con esto, no:
await descargarInforme('../../../../etc/passwd');
// path.join('informes', '../../../../etc/passwd') -> '../../../etc/passwd'
// El proceso lee /etc/passwd y lo devuelve por la red.join normaliza, pero no protege: aplica los .. con toda la corrección del mundo y te saca del directorio. Y hay más variantes: un nombrePedido absoluto (/etc/passwd) escapa aún más fácil con resolve, y los atacantes prueban además codificaciones (%2e%2e%2f), barras invertidas y bytes nulos.
La defensa correcta tiene tres pasos y no depende de listas negras:
// src/utiles/ruta-segura.js
// Resuelve una ruta pedida desde fuera dentro de un directorio base,
// garantizando que no se escapa de el.
const path = require('node:path');
function resolverDentroDe(directorioBase, rutaPedida) {
// 1. Base absoluta y definitiva.
const base = path.resolve(directorioBase);
// 2. Resolver el destino CONTRA la base. resolve aplica los '..'.
const destino = path.resolve(base, rutaPedida);
// 3. Comprobar que el destino sigue dentro. Con path.relative:
// si empieza por '..' o es absoluta, se ha salido.
const desdeLaBase = path.relative(base, destino);
const dentro = desdeLaBase !== '' &&
!desdeLaBase.startsWith('..') &&
!path.isAbsolute(desdeLaBase);
if (!dentro) {
const error = new Error('Ruta fuera del directorio permitido');
error.codigo = 'RUTA_NO_PERMITIDA';
throw error;
}
return destino;
}
module.exports = { resolverDentroDe };Por qué esta comprobación es la buena:
- Se valida la ruta ya resuelta, no la cadena de entrada. Da igual cómo esté escrita —
..,., barras repetidas, mezcla de separadores—: cuandoresolvetermina, solo queda una ruta canónica, y esa es la que se juzga. - Se usa
path.relativeen lugar dedestino.startsWith(base). La comparación de prefijos tiene un fallo sutil:/datos/informes-privadosempieza por/datos/informesy pasaría el filtro siendo otro directorio. Comparar prefijos exige acordarse de añadir el separador;relativeno se equivoca. - Rechaza también la cadena vacía, que significa "el propio directorio base" y casi nunca es un fichero válido que servir.
Queda un caso que path no puede resolver solo: si dentro de informes/ hay un enlace simbólico que apunta fuera, la ruta resuelta parece legítima. Para blindarlo del todo hay que comprobar el destino real con fs.realpath y volver a validar, o directamente no permitir enlaces en los directorios que se sirven. Retomaremos toda esta defensa en la lección 04-04, donde el nombre del fichero lo pondrá literalmente la URL de una petición HTTP.
src/config/rutas.js y la refactorización de Escena Viva
src/config/rutas.js y la refactorización de Escena VivaCon todo lo anterior, ya podemos arreglar la deuda de las dos lecciones previas. La idea es tener un único fichero que sepa dónde está cada cosa, y que todo el proyecto lo consulte:
// src/config/rutas.js
// Unica fuente de verdad sobre la ubicacion de los ficheros del proyecto.
// Todas las rutas son ABSOLUTAS y se anclan al codigo, no al directorio
// desde el que se ejecute el proceso.
const path = require('node:path');
// __dirname es <raiz>/src/config, asi que subimos dos niveles.
const RAIZ = path.resolve(__dirname, '..', '..');
const DIRECTORIO_DATOS = path.join(RAIZ, 'datos');
const DIRECTORIO_INFORMES = path.join(RAIZ, 'informes');
const FICHERO_EVENTOS = path.join(DIRECTORIO_DATOS, 'eventos.json');
const FICHERO_VENTAS = path.join(DIRECTORIO_DATOS, 'ventas.csv');
module.exports = {
RAIZ,
DIRECTORIO_DATOS,
DIRECTORIO_INFORMES,
FICHERO_EVENTOS,
FICHERO_VENTAS
};Los cambios en el resto del proyecto son pequeños y de una sola línea cada uno:
// src/catalogo-datos.js
const { FICHERO_EVENTOS } = require('./config/rutas.js');
// Antes: const RUTA_EVENTOS = 'datos/eventos.json';
// Ahora la constante viene de la configuracion y es absoluta.
const contenido = await fs.readFile(FICHERO_EVENTOS, 'utf8');// src/informes/almacen-informes.js
const path = require('node:path');
const { DIRECTORIO_INFORMES } = require('../config/rutas.js');
async function guardarInforme(contenido, { fecha = new Date(), sobrescribir = false } = {}) {
const { mes, dia } = particionarFecha(fecha);
const directorio = path.join(DIRECTORIO_INFORMES, mes);
const ruta = path.join(directorio, `ocupacion-${dia}.json`);
// ...el resto queda igual
}Y en la rotación de registros del ejercicio 3 de la lección anterior, el troceado manual desaparece:
// Antes:
// const directorio = ruta.slice(0, ruta.lastIndexOf('/')) || '.';
// const base = ruta.slice(ruta.lastIndexOf('/') + 1);
const directorio = path.dirname(ruta);
const base = path.basename(ruta);Cuatro ventajas concretas de centralizar:
- El proyecto funciona desde cualquier directorio, porque todo cuelga de
RAIZ, que se calcula a partir de__dirname. - Cambiar la ubicación de una carpeta es editar una línea, no buscar veintitrés cadenas repetidas por el código.
- Las pruebas del Módulo 9 pueden sustituir el módulo entero por uno que apunte a un directorio temporal.
- Se ve de un vistazo qué ficheros toca la aplicación, que es información valiosa para revisar seguridad y despliegue.
Comprobación de que la deuda está saldada:
cd /tmp && node ~/escena-viva/src/catalogo.js --tabla
# La tabla sale igual que desde la raiz del proyecto.Errores Comunes y Consejos
- Construir rutas con
+o plantillas de texto. Siemprepath.joinopath.resolve. - Creer que
pathtoca el disco. No comprueba existencia ni resuelve enlaces: es manipulación de texto. Para lo otro,fs.statyfs.realpath. - Usar
path.resolvecon un segmento que podría ser absoluto. Descarta todo lo anterior. Si el segmento viene de fuera, valida antes conresolverDentroDe. - Validar con
destino.startsWith(base). Deja pasar directorios hermanos con prefijo común. Usapath.relative. - Confiar en
process.cwd()para los datos del proyecto. Funciona en tu terminal y se rompe encron, en Docker y en las pruebas. - Usar
path.sepen URLs. Las URLs son POSIX en todas las plataformas:path.posix.join. - Consejo: guarda siempre rutas absolutas en variables y objetos internos. Convierte a relativa solo al mostrarla al usuario, con
path.relative(RAIZ, ruta). - Consejo: en módulos ES no existe
__dirname; se obtiene conpath.dirname(fileURLToPath(import.meta.url)), como viste en la lección 02-07.
Ejercicios
Ejercicio 1: inspector de rutas
Escribe src/laboratorio/inspeccionar-ruta.js que reciba una ruta por process.argv y muestre por stdout una tabla con: la ruta tal cual, si es absoluta, su resolución con resolve desde process.cwd(), su dirname, basename, extname, el resultado de parse, y su ruta relativa desde RAIZ. Pruébalo con datos/eventos.json, /etc/hosts y ../../x/y.txt, ejecutándolo desde dos directorios distintos y explicando las diferencias.
Ejercicio 2: batería de pruebas de resolverDentroDe
Escribe src/laboratorio/probar-ruta-segura.js que ejecute resolverDentroDe(DIRECTORIO_INFORMES, entrada) sobre esta lista y muestre para cada caso si se permitió o se rechazó: '2026-08/ocupacion.json', '../datos/eventos.json', '/etc/passwd', '2026-08/../2026-07/x.json', '..', '', './2026-08/./x.json' y '2026-08/../../../../etc/passwd'. Razona cada resultado antes de ejecutarlo.
Ejercicio 3: migrar el almacén de informes
Refactoriza src/informes/almacen-informes.js por completo para que no quede ni una sola ruta construida con texto: guardarInforme, listarInformes y purgarInformes deben usar path.join sobre DIRECTORIO_INFORMES, y listarInformes debe devolver además un campo rutaRelativa calculado con path.relative(RAIZ, ruta), más legible para mostrar por consola.
Soluciones
Solución 1. Lo interesante no es el código sino el experimento. Ejecutado desde la raíz del proyecto y desde /tmp, la ruta datos/eventos.json da dos resoluciones distintas:
cd ~/escena-viva && node src/laboratorio/inspeccionar-ruta.js datos/eventos.json
# resuelta: /home/ana/escena-viva/datos/eventos.json
cd /tmp && node ~/escena-viva/src/laboratorio/inspeccionar-ruta.js datos/eventos.json
# resuelta: /tmp/datos/eventos.json/etc/hosts da lo mismo en los dos casos porque ya es absoluta, y ../../x/y.txt cambia igual que el primero. Esa es exactamente la diferencia entre process.cwd() y __dirname en una sola pantalla. Nota que path.parse('/etc/hosts') devuelve ext: '' y name: 'hosts': no hay extensión que extraer.
Solución 2. Los resultados, con DIRECTORIO_INFORMES como base:
| Entrada | Resultado | Por qué |
|---|---|---|
2026-08/ocupacion.json |
Permitida | Cae dentro; relative da 2026-08/ocupacion.json |
../datos/eventos.json |
Rechazada | relative da ../datos/eventos.json, empieza por .. |
/etc/passwd |
Rechazada | resolve la toma como absoluta y descarta la base |
2026-08/../2026-07/x.json |
Permitida | Los .. se cancelan dentro de la base |
.. |
Rechazada | Apunta al padre de la base |
'' (vacía) |
Rechazada | relative da '': es la propia base, no un fichero |
./2026-08/./x.json |
Permitida | Los . desaparecen al normalizar |
2026-08/../../../../etc/passwd |
Rechazada | Cuatro niveles arriba salen de la base |
La conclusión práctica: no hay que enumerar los ataques. La validación no busca .. ni caracteres raros en la entrada; resuelve primero y pregunta después dónde ha ido a parar. Cualquier codificación exótica que el atacante invente acaba, tras resolve, en una ruta canónica que se juzga igual que las demás.
Solución 3. El patrón se repite en las tres funciones: sustituir cada plantilla por path.join y añadir la ruta relativa en la salida.
const path = require('node:path');
const { RAIZ, DIRECTORIO_INFORMES } = require('../config/rutas.js');
// En listarInformes, dentro del bucle de ficheros:
const ruta = path.join(directorio, fichero.name);
const info = await fs.stat(ruta);
informes.push({
mes,
fichero: fichero.name,
ruta, // absoluta: para trabajar
rutaRelativa: path.relative(RAIZ, ruta), // relativa: para mostrar
tamanoBytes: info.size,
modificado: info.mtime.toISOString()
});Fíjate en el criterio que aparece aquí y que conviene adoptar en todo el proyecto: absoluta para operar, relativa solo para presentar. Un console.table con rutas absolutas de setenta caracteres es ilegible; una ruta relativa guardada en una variable y usada más tarde es una bomba de relojería.
Conclusión
Las rutas de Escena Viva ya no dependen de dónde ejecutes el programa. Has visto por qué concatenar cadenas de rutas es un error —separadores duplicados, separador equivocado, .. sin resolver y, sobre todo, un segmento absoluto que anula tu directorio base— y conoces la diferencia exacta entre join, que pega y normaliza conservando el carácter relativo, y resolve, que procesa de derecha a izquierda hasta formar una ruta absoluta y descarta todo lo anterior en cuanto encuentra un segmento absoluto.
Sabes descomponer rutas con basename, dirname, extname, parse y format —con la trampa del base que gana a name + ext—, y tienes claro el error clásico que hace que un programa funcione en tu carpeta y falle desde otra: fs resuelve las rutas relativas contra process.cwd(), no contra el fichero donde las escribiste. De ahí la regla: datos del proyecto anclados a __dirname; argumentos del usuario, contra process.cwd().
Distingues path.posix de path.win32 y sabes que la frontera está en si la ruta es un acceso al disco o un identificador que viajará en una URL. Y has construido la defensa contra el recorrido de directorios: resolver contra la base y comprobar con path.relative que el destino no se ha escapado, en lugar de perseguir .. en la entrada o comparar prefijos de texto.
Escena Viva tiene ahora src/config/rutas.js con RAIZ, DIRECTORIO_DATOS, DIRECTORIO_INFORMES, FICHERO_EVENTOS y FICHERO_VENTAS, y las dos lecciones anteriores están refactorizadas para usarlo. El proyecto se ejecuta correctamente desde cualquier directorio.
Ese FICHERO_VENTAS que hemos declarado apunta a un fichero que todavía no existe, y no es casualidad. En Trabajando con Streams llega el problema que readFile no puede resolver: el histórico de ventas de una temporada no cabe en el montículo de V8. Veremos qué es un stream y sus cuatro tipos, por qué son EventEmitter de los que ya conoces, qué significa realmente la contrapresión y qué pasa cuando se ignora, y procesaremos datos/ventas.csv línea a línea con memoria constante.
Curso de Node.js: De Principiante a Avanzado
Módulo 1: Introducción a Node.js
- ¿Qué es Node.js?
- Instalación y Configuración del Entorno
- Tu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Moderno para Node.js
- El Proyecto del Curso: la Plataforma Escena Viva
Módulo 2: Conceptos Básicos
- Arquitectura de Node.js
- El Bucle de Eventos (Event Loop)
- Callbacks y Programación Asíncrona
- Promesas y async/await
- Eventos y EventEmitter
- Módulos CommonJS y require()
- Módulos ES e Interoperabilidad
Módulo 3: Sistema de Archivos y E/S
- Lectura y Escritura de Archivos
- El Módulo fs a Fondo
- Rutas Multiplataforma con el Módulo path
- Trabajando con Streams
- Streams de Transformación y pipeline
- Buffers y Datos Binarios
Módulo 4: HTTP y Servidores Web
- Creando un Servidor HTTP Simple
- Manejo de Solicitudes y Respuestas
- Enrutamiento Manual
- Sirviendo Archivos Estáticos
- Recibiendo Datos: Cuerpos de Petición y JSON
- Consumiendo APIs Externas desde Node.js
Módulo 5: NPM y Gestión de Paquetes
- Introducción a NPM y package.json
- Instalación y Uso de Paquetes
- Versionado Semántico y package-lock
- Scripts de npm y Automatización del Proyecto
- Creación y Publicación de Paquetes
- Seguridad y Mantenimiento de Dependencias
Módulo 6: Framework Express.js
- Introducción a Express.js
- Configuración de una Aplicación Express
- Enrutamiento en Express
- Middleware
- Middleware de Terceros Esenciales
- Validación de Datos de Entrada
- Manejo de Errores
Módulo 7: Bases de Datos y ORMs
- Introducción a las Bases de Datos
- Usando MongoDB con Mongoose
- Operaciones CRUD
- Relaciones, Poblado y Consultas Avanzadas
- Usando Bases de Datos SQL con Sequelize
- Migraciones, Transacciones y Datos de Prueba
Módulo 8: Autenticación y Autorización
- Introducción a la Autenticación
- Registro de Usuarios y Hash de Contraseñas
- Sesiones y Cookies con Passport.js
- Autenticación con JWT
- Control de Acceso Basado en Roles
- Buenas Prácticas de Seguridad en APIs
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con Mocha y Chai
- Dobles de Prueba con Sinon
- Pruebas de Integración
- Cobertura y Automatización de las Pruebas
- Depuración de Aplicaciones Node.js
Módulo 10: Temas Avanzados
- El Módulo Cluster
- Hilos de Trabajo (Worker Threads)
- Caché y Colas de Trabajo con Redis
- Optimización del Rendimiento
- Construcción de APIs RESTful
- GraphQL con Node.js
Módulo 11: Despliegue y DevOps
- Configuración y Variables de Entorno
- Registro y Monitorización en Producción
- Usando PM2 para la Gestión de Procesos
- Empaquetado con Docker
- Desplegando en Heroku y Otras PaaS
- Integración y Despliegue Continuos
