Express

Express

Tutorial Express: Conexión a MongoDB

Aprende a conectar MongoDB con Mongoose y definir esquemas robustos en Express 5 para aplicaciones Node.js profesionales.

Aprende Express y certifícate

Mongoose setup

Mongoose es el ODM (Object Document Mapper) más utilizado para trabajar con MongoDB en aplicaciones Node.js. A diferencia del driver nativo de MongoDB, Mongoose proporciona una capa de abstracción que incluye validación de esquemas, middleware y una sintaxis más intuitiva para las operaciones de base de datos.

Instalación de Mongoose

Para integrar Mongoose en tu aplicación Express 5, necesitas instalarlo como dependencia del proyecto:

npm install mongoose

Mongoose incluye internamente el driver de MongoDB, por lo que no necesitas instalar paquetes adicionales para la conectividad básica.

Configuración de la conexión

La configuración inicial de Mongoose requiere establecer la conexión con tu instancia de MongoDB. En Express 5, la mejor práctica es configurar esta conexión durante el arranque de la aplicación:

import express from 'express';
import mongoose from 'mongoose';

const app = express();

// Configuración de la conexión a MongoDB
const connectDB = async () => {
  try {
    await mongoose.connect('mongodb://localhost:27017/mi-aplicacion', {
      // Opciones de conexión recomendadas
    });
    console.log('Conectado a MongoDB');
  } catch (error) {
    console.error('Error conectando a MongoDB:', error);
    process.exit(1);
  }
};

// Inicializar conexión
connectDB();

app.listen(3000, () => {
  console.log('Servidor Express ejecutándose en puerto 3000');
});

Gestión de eventos de conexión

Mongoose emite eventos específicos que permiten monitorear el estado de la conexión. Configurar estos eventos proporciona mejor control sobre el comportamiento de tu aplicación:

import mongoose from 'mongoose';

// Evento de conexión exitosa
mongoose.connection.on('connected', () => {
  console.log('Mongoose conectado a MongoDB');
});

// Evento de error de conexión
mongoose.connection.on('error', (error) => {
  console.error('Error de conexión Mongoose:', error);
});

// Evento de desconexión
mongoose.connection.on('disconnected', () => {
  console.log('Mongoose desconectado de MongoDB');
});

// Manejo de cierre graceful
process.on('SIGINT', async () => {
  await mongoose.connection.close();
  console.log('Conexión Mongoose cerrada por terminación de aplicación');
  process.exit(0);
});

Configuración con variables de entorno

Para aplicaciones en producción, es fundamental externalizar la configuración de conexión utilizando variables de entorno:

import dotenv from 'dotenv';
import mongoose from 'mongoose';

dotenv.config();

const connectDB = async () => {
  try {
    const mongoURI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mi-aplicacion';
    
    await mongoose.connect(mongoURI, {
      maxPoolSize: 10, // Máximo de conexiones simultáneas
      serverSelectionTimeoutMS: 5000, // Timeout para selección de servidor
      socketTimeoutMS: 45000, // Timeout para operaciones de socket
    });
    
    console.log(`Conectado a MongoDB: ${mongoose.connection.host}`);
  } catch (error) {
    console.error('Fallo en conexión a MongoDB:', error.message);
    process.exit(1);
  }
};

Integración con Express 5

En Express 5, puedes modularizar la configuración de Mongoose creando un archivo dedicado para la configuración de base de datos:

config/database.js:

import mongoose from 'mongoose';

class DatabaseConnection {
  constructor() {
    this.isConnected = false;
  }

  async connect() {
    if (this.isConnected) {
      console.log('Ya existe una conexión activa a MongoDB');
      return;
    }

    try {
      const connection = await mongoose.connect(process.env.MONGODB_URI);
      this.isConnected = connection.connections[0].readyState === 1;
      
      console.log('Base de datos conectada exitosamente');
    } catch (error) {
      console.error('Error en conexión a base de datos:', error);
      throw error;
    }
  }

  async disconnect() {
    if (!this.isConnected) return;
    
    await mongoose.disconnect();
    this.isConnected = false;
    console.log('Desconectado de MongoDB');
  }
}

export default new DatabaseConnection();

app.js:

import express from 'express';
import database from './config/database.js';

const app = express();

// Inicializar conexión a base de datos
const startServer = async () => {
  try {
    await database.connect();
    
    app.listen(3000, () => {
      console.log('Servidor Express 5 ejecutándose en puerto 3000');
    });
  } catch (error) {
    console.error('Error iniciando servidor:', error);
    process.exit(1);
  }
};

startServer();

Configuración de opciones avanzadas

Mongoose permite personalizar el comportamiento de la conexión mediante diversas opciones que optimizan el rendimiento y la estabilidad:

const connectionOptions = {
  // Pool de conexiones
  maxPoolSize: 10,
  minPoolSize: 5,
  
  // Timeouts
  serverSelectionTimeoutMS: 5000,
  socketTimeoutMS: 45000,
  connectTimeoutMS: 10000,
  
  // Configuración de heartbeat
  heartbeatFrequencyMS: 10000,
  
  // Configuración de retry
  retryWrites: true,
  retryReads: true,
};

await mongoose.connect(mongoURI, connectionOptions);

Esta configuración establece las bases sólidas para trabajar con MongoDB en tu aplicación Express 5, proporcionando manejo robusto de errores, conexiones eficientes y una arquitectura escalable que facilita el mantenimiento del código.

Esquemas básicos

Los esquemas de Mongoose definen la estructura y las reglas de validación para los documentos que se almacenan en MongoDB. A diferencia de MongoDB que es una base de datos sin esquema, Mongoose introduce esta capa de tipado y validación que aporta consistencia y robustez a las aplicaciones Express.

Definición de esquemas

Un esquema en Mongoose se crea utilizando la clase Schema y especifica los campos, tipos de datos y reglas de validación para una colección:

import mongoose from 'mongoose';

const { Schema } = mongoose;

// Definición básica de esquema
const usuarioSchema = new Schema({
  nombre: String,
  email: String,
  edad: Number,
  activo: Boolean,
  fechaRegistro: Date
});

// Crear modelo a partir del esquema
const Usuario = mongoose.model('Usuario', usuarioSchema);

export default Usuario;

Tipos de datos fundamentales

Mongoose proporciona tipos de datos específicos que se mapean directamente con los tipos nativos de JavaScript y MongoDB:

const productoSchema = new Schema({
  // Tipos primitivos
  nombre: String,
  precio: Number,
  disponible: Boolean,
  fechaCreacion: Date,
  
  // Tipos especiales de Mongoose
  id: mongoose.Schema.Types.ObjectId,
  buffer: Buffer,
  mixto: mongoose.Schema.Types.Mixed,
  
  // Arrays
  etiquetas: [String],
  valoraciones: [Number],
  
  // Objetos anidados
  direccion: {
    calle: String,
    ciudad: String,
    codigoPostal: String
  }
});

Validaciones básicas

Los esquemas permiten definir reglas de validación que se ejecutan automáticamente antes de guardar documentos en la base de datos:

const clienteSchema = new Schema({
  nombre: {
    type: String,
    required: true,
    trim: true,
    minlength: 2,
    maxlength: 50
  },
  
  email: {
    type: String,
    required: [true, 'El email es obligatorio'],
    unique: true,
    lowercase: true,
    match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Email inválido']
  },
  
  edad: {
    type: Number,
    min: [18, 'Debe ser mayor de edad'],
    max: [120, 'Edad no válida']
  },
  
  telefono: {
    type: String,
    validate: {
      validator: function(v) {
        return /\d{9}/.test(v);
      },
      message: 'El teléfono debe tener 9 dígitos'
    }
  }
});

Valores por defecto y transformaciones

Los esquemas pueden incluir valores predeterminados y funciones que se ejecutan automáticamente durante la creación o modificación de documentos:

const pedidoSchema = new Schema({
  numero: {
    type: String,
    default: function() {
      return 'PED-' + Date.now();
    }
  },
  
  estado: {
    type: String,
    enum: ['pendiente', 'procesando', 'enviado', 'entregado'],
    default: 'pendiente'
  },
  
  fechaCreacion: {
    type: Date,
    default: Date.now
  },
  
  total: {
    type: Number,
    required: true,
    set: function(valor) {
      return Math.round(valor * 100) / 100; // Redondear a 2 decimales
    }
  }
});

Uso de esquemas en rutas Express

Una vez definidos los esquemas, puedes utilizarlos en las rutas de Express para realizar operaciones CRUD:

import express from 'express';
import Usuario from '../models/Usuario.js';

const router = express.Router();

// Crear nuevo usuario
router.post('/usuarios', async (req, res) => {
  try {
    const nuevoUsuario = new Usuario(req.body);
    const usuarioGuardado = await nuevoUsuario.save();
    
    res.status(201).json(usuarioGuardado);
  } catch (error) {
    if (error.name === 'ValidationError') {
      return res.status(400).json({ 
        error: 'Datos inválidos', 
        detalles: error.message 
      });
    }
    res.status(500).json({ error: 'Error interno del servidor' });
  }
});

// Obtener usuarios
router.get('/usuarios', async (req, res) => {
  try {
    const usuarios = await Usuario.find({ activo: true });
    res.json(usuarios);
  } catch (error) {
    res.status(500).json({ error: 'Error al obtener usuarios' });
  }
});

Esquemas con referencias

Los esquemas pueden establecer relaciones entre colecciones utilizando referencias a otros documentos:

const comentarioSchema = new Schema({
  contenido: {
    type: String,
    required: true,
    maxlength: 500
  },
  
  autor: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Usuario',
    required: true
  },
  
  articulo: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Articulo',
    required: true
  },
  
  fechaCreacion: {
    type: Date,
    default: Date.now
  }
});

const Comentario = mongoose.model('Comentario', comentarioSchema);

Para utilizar estas referencias, puedes emplear el método populate() que resuelve automáticamente las referencias:

// Obtener comentarios con información del autor
router.get('/comentarios', async (req, res) => {
  try {
    const comentarios = await Comentario
      .find()
      .populate('autor', 'nombre email')
      .populate('articulo', 'titulo');
      
    res.json(comentarios);
  } catch (error) {
    res.status(500).json({ error: 'Error al obtener comentarios' });
  }
});

Organización de modelos

Para mantener una estructura ordenada en aplicaciones Express 5, organiza los esquemas en archivos separados dentro de una carpeta models:

models/index.js:

import Usuario from './Usuario.js';
import Producto from './Producto.js';
import Pedido from './Pedido.js';

export {
  Usuario,
  Producto,
  Pedido
};

routes/api.js:

import express from 'express';
import { Usuario, Producto, Pedido } from '../models/index.js';

const router = express.Router();

// Las rutas pueden acceder a todos los modelos
router.get('/dashboard', async (req, res) => {
  try {
    const stats = {
      usuarios: await Usuario.countDocuments(),
      productos: await Producto.countDocuments(),
      pedidos: await Pedido.countDocuments()
    };
    
    res.json(stats);
  } catch (error) {
    res.status(500).json({ error: 'Error al obtener estadísticas' });
  }
});

Los esquemas de Mongoose proporcionan una base sólida para estructurar los datos en aplicaciones Express, combinando la flexibilidad de MongoDB con las garantías de validación y consistencia que requieren las aplicaciones profesionales.

Aprende Express online

Otras lecciones de Express

Accede a todas las lecciones de Express y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Express y certifícate

Ejercicios de programación de Express

Evalúa tus conocimientos de esta lección Conexión a MongoDB con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.