En la lección anterior decidimos el modelo de datos de Escena Viva y dejamos preparada la frontera src/repositorios/. Ahora bajamos al código: conectaremos la aplicación a MongoDB, escribiremos el módulo de conexión que se integra con el apagado ordenado de src/servidor.js, y traduciremos el diagrama de la lección 07-01 a esquemas y modelos de Mongoose en src/modelos/.
Esta lección no escribe consultas todavía —eso es la 07-03—. Escribe la estructura: qué forma tienen los documentos, qué reglas hacen cumplir, qué campos derivados exponen y qué índices los sostienen.
Contenido
- Qué es MongoDB: documentos, colecciones y
ObjectId - Qué es Mongoose y si merece la pena
- Conexión, arranque y apagado ordenado
- El esquema
Eventocon sesiones incrustadas - Tipos y opciones de esquema
Pedido,Entraday unUsuariomínimo- Validadores de esquema frente a validación zod
_idfrente a identificadores de negocio- Métodos, estáticos y virtuals
- Middleware de esquema y
toJSON - Índices declarados
Qué es MongoDB: documentos, colecciones y ObjectId
MongoDB guarda documentos en colecciones. Un documento es una estructura parecida a un objeto JavaScript, con campos anidados y arrays. Una colección es el equivalente laxo de una tabla: un contenedor que no exige que todos sus documentos tengan la misma forma.
El formato interno es BSON (Binary JSON): una codificación binaria que añade tipos que JSON no tiene y que a nosotros nos importan.
| Tipo BSON | Equivalente JS | Por qué importa en Escena Viva |
|---|---|---|
ObjectId |
Objeto de 12 bytes | Identificador por defecto de todo documento |
Date |
Date |
fechaHora se guarda como fecha real, no como texto |
Int32 / Int64 |
number |
aforo, vendidas, precioCentimos son enteros de verdad |
Decimal128 / Binary |
— / Buffer |
Decimal exacto (no lo usamos: vamos en céntimos) y buffers del M3 |
Todo documento tiene _id único en su colección. Si no lo das, MongoDB genera un ObjectId: 12 bytes que combinan marca de tiempo, identificador de máquina/proceso y un contador. Dos consecuencias prácticas: son únicos sin coordinación central y son aproximadamente ordenables por fecha de creación, porque los primeros 4 bytes son el instante en segundos.
En texto se ve como 507f1f77bcf86cd799439011: 24 caracteres hexadecimales. Ojo con esto, porque pasar una cadena que no tiene esa forma a una consulta por _id produce un CastError, y ese error hay que traducirlo a un 400, no dejar que se convierta en un 500.
Qué es Mongoose y si merece la pena
Mongoose es un ODM: se sitúa sobre el controlador oficial mongodb y aporta esquema (la forma esperada de tus documentos), validación antes de escribir, tipado y conversión automática, y funcionalidad de modelo: hooks pre/post, métodos, virtuals y populate.
¿Merece la pena frente al controlador nativo? La respuesta honesta: depende de cuánta disciplina puedas garantizar sin ayuda. Con el controlador nativo escribes exactamente la consulta que se ejecuta y no hay sorpresas de rendimiento, pero cada campo obligatorio, cada valor por defecto y cada conversión de tipo es código tuyo repetido en cada punto de escritura. Mongoose centraliza eso y hace que un documento mal formado sea difícil de crear por accidente. El coste es una capa de indirección: hay que saber que find() devuelve un Query perezoso, que lean() cambia lo que recibes y que un pre('save') no se dispara en un updateOne. Para un dominio con invariantes como el nuestro la balanza se inclina hacia Mongoose; para un script de ingesta de un millón de documentos, bajaría al controlador nativo sin dudarlo.
Se instala con npm install mongoose; incluye el controlador mongodb como dependencia, así que no hay que instalarlo aparte.
Conexión, arranque y apagado ordenado
La URL entra por el único sitio autorizado a leer process.env: src/config/index.js.
// src/config/index.js (fragmento nuevo)
const mongodbUrl = process.env.MONGODB_URL ?? 'mongodb://localhost:27017/escena_viva';
if (!/^mongodb(\+srv)?:\/\//.test(mongodbUrl)) {
throw new Error('MONGODB_URL debe empezar por mongodb:// o mongodb+srv://');
}
const configuracion = Object.freeze({
mongodbUrl, // ...junto a los campos anteriores del modulo 6
mongodbTiempoEspera: Number(process.env.MONGODB_TIEMPO_ESPERA ?? 5000),
});Validar al arrancar es deliberado: si la URL está mal queremos enterarnos al lanzar el proceso, no en la primera petición de un cliente.
// src/db/conexion.js
'use strict';
const mongoose = require('mongoose');
const { configuracion } = require('../config/index.js');
// Mongoose 7+ no permite consultar por campos no declarados en el esquema.
mongoose.set('strictQuery', true);
let conexionActiva = null;
/** Abre la conexion. Es idempotente: si ya hay una, la reutiliza. */
async function conectar({ url = configuracion.mongodbUrl } = {}) {
if (conexionActiva) return conexionActiva;
registrarEventos();
await mongoose.connect(url, {
// Si no encuentra servidor en este tiempo, rechaza en vez de esperar siempre.
serverSelectionTimeoutMS: configuracion.mongodbTiempoEspera,
// Pool de sockets reutilizados: operaciones simultaneas contra el motor.
maxPoolSize: 10,
minPoolSize: 1,
});
conexionActiva = mongoose.connection;
return conexionActiva;
}
async function desconectar() {
if (!conexionActiva) return;
await mongoose.disconnect();
conexionActiva = null;
}
function registrarEventos() {
const { connection } = mongoose;
// No registramos la URL completa: puede llevar credenciales.
connection.on('connected', () => console.log(`[db] conectado a '${connection.name}'`));
connection.on('error', (error) => console.error('[db] error:', error.message));
connection.on('disconnected', () => console.warn('[db] desconectado; reintentara'));
}
module.exports = { conectar, desconectar };Tres detalles que no son adorno. serverSelectionTimeoutMS: sin él, una URL equivocada deja el arranque colgado sin explicación. El pool: Mongoose no abre una conexión por consulta, mantiene un conjunto de sockets; si maxPoolSize fuera 1, serializarías toda la aplicación. Los eventos: tras un disconnected el controlador reintenta automáticamente y encola las operaciones pendientes; no necesitas escribir reconexión, pero sí registrar el evento para no quedarte ciego durante una caída.
crearAplicacion() sigue siendo una factoría pura que no toca la red. La conexión pertenece al proceso, y el proceso lo gobierna src/servidor.js:
// src/servidor.js (fragmento adaptado)
async function arrancarServidor() {
await conectar(); // 1. Primero la base: sin datos no tiene sentido dar trafico.
const servidor = crearAplicacion().listen(configuracion.puerto);
async function apagar(senal) {
console.log(`[http] recibida ${senal}, apagando de forma ordenada`);
// 2. Dejamos de aceptar peticiones y, solo cuando las que estan en vuelo
// han terminado, 3. cerramos la conexion con la base de datos.
servidor.close(async () => {
await desconectar();
process.exit(0);
});
}
process.on('SIGTERM', () => apagar('SIGTERM'));
process.on('SIGINT', () => apagar('SIGINT'));
return servidor;
}El orden importa y es simétrico. Al arrancar: base primero, HTTP después, para que ninguna petición llegue a una aplicación sin datos. Al apagar: HTTP primero, base después, porque cerrar la conexión con peticiones en vuelo produciría errores en compras ya en curso. La conexión es un recurso del proceso, con su mismo ciclo de vida.
El esquema Evento con sesiones incrustadas
// src/modelos/evento.js
'use strict';
const mongoose = require('mongoose');
const { Schema } = mongoose;
const ESTADOS_EVENTO = ['borrador', 'publicado', 'finalizado'];
const esquemaSesion = new Schema(
{
// Identificador de negocio, con el formato ses-NNN-M que ya valida zod.
sesionId: { type: String, required: true, match: /^ses-\d{3}-\d+$/ },
fechaHora: { type: Date, required: true },
aforo: { type: Number, required: true, min: 1 },
vendidas: { type: Number, required: true, default: 0, min: 0 },
precioCentimos: { type: Number, required: true, min: 0 },
},
// Los subdocumentos no necesitan _id: los identifica sesionId.
{ _id: false },
);
// Validador personalizado: la invariante central del dominio.
esquemaSesion.path('vendidas').validate(function comprobarAforo(valor) {
return valor <= this.aforo;
}, 'Las entradas vendidas no pueden superar el aforo');
const esquemaEvento = new Schema(
{
eventoId: { type: String, required: true, unique: true, match: /^evt-\d{3}$/ },
titulo: { type: String, required: true, trim: true, maxlength: 160 },
sala: { type: String, required: true, trim: true, index: true },
organizadorId: { type: String, required: true, index: true },
categoria: { type: String, required: true, trim: true },
estado: { type: String, required: true, enum: ESTADOS_EVENTO, default: 'borrador' },
duracionMinutos: { type: Number, required: true, min: 1, max: 600 },
sesiones: { type: [esquemaSesion], default: [] },
},
{ timestamps: true },
);
const Evento = mongoose.model('Evento', esquemaEvento);
module.exports = { Evento, ESTADOS_EVENTO };El esquema reproduce campo a campo lo que ya sabíamos del dominio: evt-001 "Concierto de Otono" en el Teatro Almendra, con org-almendra y las sesiones ses-001-1 y ses-001-2. Nada nuevo se inventa; se persiste lo que ya existía. Y { timestamps: true } añade createdAt y updatedAt automáticos: el principio del historial que echábamos de menos en el JSON.
Tipos y opciones de esquema
| Opción | Qué hace | Ejemplo |
|---|---|---|
type |
String, Number, Date, Boolean, ObjectId, arrays, subdocumentos |
fechaHora: Date |
required |
Rechaza el guardado si falta | aforo |
default |
Valor si no se indica; admite función | vendidas: 0 |
enum |
Lista cerrada de valores | estado |
min / max |
Rango numérico o de fechas | precioCentimos: { min: 0 } |
minlength / maxlength |
Longitud de cadena | titulo hasta 160 |
match |
Expresión regular | codigo: EV-\d{4}-\d{6} |
trim / lowercase |
Normaliza antes de guardar | email |
unique / index |
No validan: declaran índices (único o normal) | eventoId, sala |
immutable |
Impide modificarlo tras crearlo | codigo |
timestamps |
Opción de esquema: createdAt/updatedAt |
Todos los modelos |
La fila de unique merece énfasis: unique: true no valida nada en Mongoose. Le pide a MongoDB que cree un índice único, y quien rechaza el duplicado es el motor, con un error E11000 en tiempo de escritura, no un ValidationError. En la lección siguiente lo traduciremos a un 409.
Pedido, Entrada y un Usuario mínimo
// src/modelos/pedido.js
const ESTADOS_PEDIDO = ['pendiente', 'pagado', 'emitido', 'anulado'];
const esquemaPedido = new Schema(
{
// Referencia a otra coleccion: guarda el _id, no el usuario entero.
usuarioId: { type: Schema.Types.ObjectId, ref: 'Usuario', required: true, index: true },
eventoId: { type: String, required: true },
sesionId: { type: String, required: true, index: true },
cantidad: { type: Number, required: true, min: 1, max: 6 },
totalCentimos: { type: Number, required: true, min: 0 },
estado: { type: String, required: true, enum: ESTADOS_PEDIDO, default: 'pendiente' },
canal: { type: String, required: true, enum: ['web', 'taquilla', 'telefono'] },
},
{ timestamps: true },
);
const Pedido = mongoose.model('Pedido', esquemaPedido);
module.exports = { Pedido, ESTADOS_PEDIDO };// src/modelos/entrada.js
const ESTADOS_ENTRADA = ['valida', 'usada', 'anulada'];
const esquemaEntrada = new Schema(
{
// El codigo impreso: EV-<anio>-<6 digitos>. Unico e inmutable.
codigo: { type: String, required: true, unique: true, immutable: true, match: /^EV-\d{4}-\d{6}$/ },
pedidoId: { type: Schema.Types.ObjectId, ref: 'Pedido', required: true, index: true },
sesionId: { type: String, required: true, index: true },
estado: { type: String, required: true, enum: ESTADOS_ENTRADA, default: 'valida' },
// Momento del escaneo en la puerta; nulo mientras no se ha usado.
usadaEn: { type: Date, default: null },
},
{ timestamps: true },
);
const Entrada = mongoose.model('Entrada', esquemaEntrada);
module.exports = { Entrada, ESTADOS_ENTRADA };
// --- src/modelos/usuario.js ---
const ROLES = ['asistente', 'organizador', 'administrador'];
const esquemaUsuario = new Schema(
{
email: { type: String, required: true, unique: true, lowercase: true, trim: true },
nombre: { type: String, required: true, trim: true },
rol: { type: String, required: true, enum: ROLES, default: 'asistente' },
// Aqui NO hay contrasena. La autenticacion es el modulo 8 al completo:
// hash, sesiones, JWT y control de acceso por rol.
},
{ timestamps: true },
);
const Usuario = mongoose.model('Usuario', esquemaUsuario);
module.exports = { Usuario, ROLES };Ese comentario en Usuario no es un despiste: es una decisión. El usuario existe para poder referenciarlo desde los pedidos y para que los roles ya estén modelados, pero no implementamos autenticación. Guardar contraseñas mal es peor que no guardarlas.
Validadores de esquema frente a validación zod
En el módulo 6 pusimos zod en el borde, con esquemaCrearPedido y el middleware validar que deja el resultado en req.datosValidados. Ahora aparece la validación del esquema de Mongoose. ¿No es duplicar trabajo? No: son capas distintas, con clientes distintos.
| zod (borde HTTP) | Mongoose (esquema) | |
|---|---|---|
| Qué valida | La forma del cuerpo de la petición | La forma del documento antes de escribirlo |
| Quién la dispara | Cada petición HTTP | Cada save() o create(), venga de donde venga |
| Protege de | Clientes que envían basura | Bugs propios, scripts, migraciones, semillas |
| Error que produce | ErrorDeValidacion → 400/422 |
ValidationError de Mongoose |
La regla en una frase: zod valida lo que entra por la puerta; Mongoose valida lo que sale hacia el disco. Un script de importación nocturno no pasa por Express y por tanto no pasa por zod; si el esquema no validara, podría dejar vendidas: 9999 en una sesión de aforo 300. Y zod puede rechazar cosas que a Mongoose le dan igual (un campo extra, un canal desconocido) antes de gastar un viaje a la base. Por encima de ambas sigue el dominio: Sesion.vender() conserva su invariante en memoria. Tres capas, tres momentos, ninguna redundante.
_id frente a identificadores de negocio
Nuestros documentos tienen dos identificadores. El _id es técnico: único, generado sin coordinación, eficiente como clave de índice y destino de las referencias que usa populate. El identificador de negocio (evt-001, ses-001-1, EV-2026-000431) es semántico: aparece en URLs, correos, entradas impresas, los CSV del módulo 3 y los esquemas zod del módulo 6; y es estable frente a migraciones, porque si mañana movemos los datos a PostgreSQL los _id desaparecen pero evt-001 sigue siendo evt-001. Usar _id en URLs públicas te ata al motor y filtra información interna; usar solo el de negocio como _id es defendible, pero pierdes la ordenación temporal implícita. Conservar ambos, con índice único sobre el de negocio, es lo habitual en sistemas que esperan vivir años.
Métodos, estáticos y virtuals
Los virtuals reproducen los getters ricos del dominio del módulo 2: campos calculados que no se guardan.
// src/modelos/evento.js (ampliación)
esquemaSesion.virtual('libres').get(function () {
return this.aforo - this.vendidas;
});
esquemaSesion.virtual('ocupacion').get(function () {
return this.aforo === 0 ? 0 : Number((this.vendidas / this.aforo).toFixed(4));
});
esquemaSesion.virtual('agotada').get(function () {
return this.vendidas >= this.aforo;
});
esquemaEvento.virtual('aforoTotal').get(function () {
return this.sesiones.reduce((total, sesion) => total + sesion.aforo, 0);
});
// Metodo de instancia: opera sobre UN documento.
esquemaEvento.methods.buscarSesion = function (sesionId) {
return this.sesiones.find((sesion) => sesion.sesionId === sesionId) ?? null;
};
// Metodo estatico: opera sobre el modelo (la coleccion entera).
esquemaEvento.statics.buscarPorSala = function (sala) {
return this.find({ sala, estado: 'publicado' }).sort({ titulo: 1 });
};Dos avisos. Los virtuals no existen en la base de datos: no puedes filtrar ni ordenar por ocupacion, porque MongoDB no sabe que existe; para eso hace falta una agregación (lección 07-04) o guardarlo desnormalizado. Y lean() los elimina: si pides objetos planos por rendimiento, pierdes virtuals y métodos.
Sobre function frente a arrow: en métodos, virtuals y hooks hay que usar function, porque Mongoose vincula this al documento. Una arrow captura el this léxico del módulo y te dejará con undefined. Es el error número uno de quien empieza.
Middleware de esquema y toJSON
// src/modelos/pedido.js: se ejecuta antes de guardar; lanzar aborta la escritura.
esquemaPedido.pre('save', function (next) {
if (this.isNew && this.totalCentimos === 0 && this.cantidad > 0) {
return next(new Error('Un pedido con entradas no puede tener total cero'));
}
next();
});Uso responsable de los hooks significa tres cosas:
- Nada de efectos externos. Enviar un correo o llamar a una API dentro de un
pre('save')acopla la persistencia a un servicio remoto: un fallo de red impediría guardar. Eso va fuera, o alGestorDeVentasque ya tenemos. - Recuerda qué hooks se disparan.
pre('save')no se ejecuta enupdateOne,findOneAndUpdatenideleteMany, porque esas operaciones ocurren en el servidor sin materializar el documento. Si la lógica es innegociable, ponla en el esquema o en el repositorio. Y que sean rápidos: un hook lento se paga en cada escritura.
// Transformacion de salida, aplicable a los cuatro esquemas.
const configurarSalida = (esquema) => esquema.set('toJSON', {
virtuals: true, // incluye libres, ocupacion, agotada...
versionKey: false, // elimina __v
transform(documento, plano) {
plano.id = documento._id.toString();
delete plano._id;
return plano;
},
});El __v es el contador interno de versión de Mongoose: no aporta nada a un cliente HTTP y solo genera preguntas. Y exponer _id en bruto acopla tu contrato público al motor. Esta transformación es la frontera entre "cómo guardo" y "qué publico", la misma separación que defendían los repositorios.
Índices declarados
Un índice acelera lecturas y encarece escrituras. Se declaran por consulta prevista, no por capricho:
// Catalogo de una sala, ordenado: primero los filtros por igualdad, luego el
// campo de ordenacion.
esquemaEvento.index({ estado: 1, sala: 1, titulo: 1 });
// Rango de fechas. Al ser un campo de un array de subdocumentos, MongoDB crea
// un indice multiclave automaticamente.
esquemaEvento.index({ 'sesiones.fechaHora': 1 });
// Localizar el evento que contiene una sesion. Unico: un sesionId no se repite.
esquemaEvento.index({ 'sesiones.sesionId': 1 }, { unique: true });| Índice | Consulta que acelera |
|---|---|
eventoId (por unique) |
GET /eventos/evt-001 |
{ estado, sala, titulo } |
Catálogo del Teatro Almendra ordenado por título |
sesiones.fechaHora |
"Qué hay en cartel del 1 al 15 de marzo" |
sesiones.sesionId (único) |
Encontrar ses-001-2 para vender |
Entrada.codigo (único) / Pedido.usuarioId |
Escaneo en puerta / "mis pedidos" |
En desarrollo Mongoose crea estos índices al arrancar (autoIndex). En producción debe desactivarse (mongoose.set('autoIndex', false)) y crearlos en la migración correspondiente: construir un índice sobre millones de documentos durante el arranque puede bloquear la aplicación varios minutos. Lo retomamos en la lección 07-06.
Errores Comunes y Consejos
- Arrow functions en métodos, virtuals o hooks.
thisdeja de ser el documento y todo devuelveundefined. - Creer que
unique: truevalida. Es un índice; el error llega comoE11000, no comoValidationError. - Llamar a
mongoose.connecten cada petición. La conexión es del proceso y tiene pool; conectar por petición agota los sockets del sistema. - Registrar la URL de conexión. Si lleva credenciales, acabas de filtrarlas. Registra
connection.name. - Definir el mismo modelo dos veces.
mongoose.model('Evento', ...)en dos ficheros lanzaOverwriteModelError. - Consejo: empieza con
requiredyenumgenerosos —es más barato relajar un esquema que limpiar datos incoherentes— y el dinero siempre entero y en céntimos.
Ejercicios
Ejercicio 1: modelo de sala
Escribe src/modelos/sala.js con salaId (único, formato sala-xxx), nombre, ciudad, aforoMaximo (entero ≥ 1) y activa (booleano, por defecto true). Añade un virtual etiqueta que devuelva "<nombre> (<ciudad>)", un estático buscarActivas(), timestamps y la transformación toJSON.
Ejercicio 2: validador cruzado
Añade a Entrada un validador que impida marcar usadaEn con una fecha cuando el estado sea anulada. Explica por qué no se dispararía con findByIdAndUpdate sin opciones.
Ejercicio 3: elegir índices
Decide el índice y el orden de sus campos para: (a) pedidos pagado de un usuario ordenados por fecha descendente; (b) entradas valida de una sesión; (c) eventos de un organizador en estado publicado.
Soluciones
Ejercicio 1.
// src/modelos/sala.js
const esquemaSala = new Schema(
{
salaId: { type: String, required: true, unique: true, match: /^sala-[a-z]+$/ },
nombre: { type: String, required: true, trim: true },
ciudad: { type: String, required: true, trim: true, index: true },
aforoMaximo: { type: Number, required: true, min: 1 },
activa: { type: Boolean, required: true, default: true },
},
{ timestamps: true },
);
esquemaSala.virtual('etiqueta').get(function () {
return `${this.nombre} (${this.ciudad})`;
});
esquemaSala.statics.buscarActivas = function () {
return this.find({ activa: true }).sort({ nombre: 1 });
};
esquemaSala.set('toJSON', { virtuals: true, versionKey: false });
module.exports = { Sala: mongoose.model('Sala', esquemaSala) };Ejercicio 2.
esquemaEntrada.path('usadaEn').validate(function comprobarUso(valor) {
// Una entrada anulada nunca puede tener marca de uso.
return !(this.estado === 'anulada' && valor !== null);
}, 'Una entrada anulada no puede registrar fecha de uso');No se dispara porque las actualizaciones se ejecutan en el servidor sin construir el documento: Mongoose solo valida si le pasas { runValidators: true }, y aun así this es la consulta, no el documento, así que un validador que depende de otros campos puede no tener acceso a ellos.
Ejercicio 3. (a) { usuarioId: 1, estado: 1, createdAt: -1 }. (b) { sesionId: 1, estado: 1 }. (c) { organizadorId: 1, estado: 1 }. La regla se llama ESR: primero los campos comparados por igualdad (Equality), luego el de ordenación (Sort) y por último los de rango (Range).
Conclusión
Escena Viva ya tiene persistencia real. Sabes qué guarda MongoDB y cómo, qué añade Mongoose por encima y a qué precio, y cómo abrir la conexión desde la configuración validada e integrarla con el arranque y el apagado ordenado. Has traducido el diagrama de la lección anterior a cuatro modelos —Evento con sus sesiones incrustadas, Pedido, Entrada y un Usuario sin contraseñas a la espera del módulo 8— con sus tipos, validadores, virtuals, middleware, toJSON limpio e índices razonados. Y has visto por qué conviven tres capas de validación sin estorbarse: zod en el borde, el esquema antes del disco y el dominio en memoria.
En la lección siguiente empezamos a mover datos: las cuatro operaciones CRUD con sus trampas —Query perezosos, lean(), paginación, runValidators, $inc, borrado lógico—, la traducción de los errores de Mongoose a nuestra jerarquía del módulo 6, y el momento que esperábamos: jubilar src/catalogo-datos.js y sustituirlo por src/repositorios/eventos.js sin que los controladores se enteren.
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
