Express

Express

Tutorial Express: Conexión a MySQL

Aprende a configurar conexión y pool en MySQL con Express para mejorar rendimiento y seguridad en tus aplicaciones Node.js.

Aprende Express y certifícate

Configuración de conexión a MySQL

La configuración de conexión a MySQL en Express requiere establecer los parámetros necesarios para que nuestra aplicación pueda comunicarse con la base de datos de forma segura y eficiente. El paquete mysql2 proporciona una interfaz moderna y optimizada que aprovecha las características más recientes de MySQL.

Instalación y configuración inicial

Para comenzar, necesitamos instalar el paquete mysql2 que ofrece soporte completo para promesas nativas y características avanzadas de MySQL:

npm install mysql2

La configuración básica requiere especificar los parámetros de conexión fundamentales. Creamos un archivo de configuración que centralice estos valores:

// config/database.js
import mysql from 'mysql2/promise';

const dbConfig = {
  host: 'localhost',
  user: 'tu_usuario',
  password: 'tu_contraseña',
  database: 'tu_base_datos',
  port: 3306
};

export default dbConfig;

Establecimiento de conexión simple

Una conexión directa nos permite realizar operaciones básicas de forma inmediata. Este enfoque es útil para aplicaciones pequeñas o durante el desarrollo:

// database/connection.js
import mysql from 'mysql2/promise';
import dbConfig from '../config/database.js';

async function createConnection() {
  try {
    const connection = await mysql.createConnection(dbConfig);
    console.log('Conexión establecida con MySQL');
    return connection;
  } catch (error) {
    console.error('Error al conectar con MySQL:', error.message);
    throw error;
  }
}

export { createConnection };

Configuración avanzada de parámetros

MySQL2 permite configurar opciones adicionales que mejoran el rendimiento y la seguridad de las conexiones. Estas configuraciones son especialmente importantes en entornos de producción:

// config/database.js
const dbConfig = {
  host: process.env.DB_HOST || 'localhost',
  user: process.env.DB_USER || 'root',
  password: process.env.DB_PASSWORD || '',
  database: process.env.DB_NAME || 'mi_app',
  port: process.env.DB_PORT || 3306,
  
  // Configuraciones de seguridad
  ssl: process.env.NODE_ENV === 'production' ? {
    rejectUnauthorized: false
  } : false,
  
  // Configuraciones de conexión
  connectTimeout: 60000,
  acquireTimeout: 60000,
  timeout: 60000,
  
  // Configuraciones de charset
  charset: 'utf8mb4',
  timezone: '+00:00'
};

export default dbConfig;

Manejo de variables de entorno

La gestión segura de credenciales requiere el uso de variables de entorno. Creamos un archivo .env para almacenar información sensible:

# .env
DB_HOST=localhost
DB_USER=mi_usuario
DB_PASSWORD=mi_contraseña_segura
DB_NAME=mi_aplicacion
DB_PORT=3306
NODE_ENV=development

Para cargar estas variables, utilizamos el paquete dotenv en nuestra configuración:

// config/database.js
import 'dotenv/config';
import mysql from 'mysql2/promise';

const dbConfig = {
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  port: parseInt(process.env.DB_PORT) || 3306,
  
  // Configuraciones adicionales
  multipleStatements: false,
  namedPlaceholders: true,
  dateStrings: true
};

export default dbConfig;

Integración con Express

La integración efectiva con Express requiere hacer la conexión accesible a través de toda la aplicación. Podemos configurar la conexión como middleware o servicio:

// services/database.js
import mysql from 'mysql2/promise';
import dbConfig from '../config/database.js';

class DatabaseService {
  constructor() {
    this.connection = null;
  }
  
  async connect() {
    try {
      this.connection = await mysql.createConnection(dbConfig);
      console.log('Servicio de base de datos inicializado');
    } catch (error) {
      console.error('Error en el servicio de base de datos:', error);
      throw error;
    }
  }
  
  async query(sql, params = []) {
    if (!this.connection) {
      await this.connect();
    }
    
    try {
      const [results] = await this.connection.execute(sql, params);
      return results;
    } catch (error) {
      console.error('Error en consulta:', error);
      throw error;
    }
  }
}

export default new DatabaseService();

Configuración en la aplicación Express

Para utilizar la configuración de base de datos en nuestra aplicación Express, integramos el servicio durante el arranque:

// app.js
import express from 'express';
import DatabaseService from './services/database.js';

const app = express();

// Middleware para parsear JSON
app.use(express.json());

// Inicializar conexión a base de datos
async function initializeApp() {
  try {
    await DatabaseService.connect();
    
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Servidor Express ejecutándose en puerto ${PORT}`);
    });
  } catch (error) {
    console.error('Error al inicializar la aplicación:', error);
    process.exit(1);
  }
}

initializeApp();

Validación de configuración

Es fundamental implementar validación de parámetros para detectar errores de configuración antes de intentar establecer la conexión:

// utils/validateConfig.js
function validateDatabaseConfig(config) {
  const requiredFields = ['host', 'user', 'password', 'database'];
  const missingFields = [];
  
  requiredFields.forEach(field => {
    if (!config[field]) {
      missingFields.push(field);
    }
  });
  
  if (missingFields.length > 0) {
    throw new Error(`Faltan parámetros de configuración: ${missingFields.join(', ')}`);
  }
  
  if (config.port && (config.port < 1 || config.port > 65535)) {
    throw new Error('El puerto debe estar entre 1 y 65535');
  }
  
  return true;
}

export { validateDatabaseConfig };

Esta configuración establece las bases sólidas para la conexión con MySQL, proporcionando flexibilidad para diferentes entornos y manteniendo la seguridad de las credenciales. La estructura modular facilita el mantenimiento y permite escalar la aplicación según las necesidades del proyecto.

Pool de conexiones

Un pool de conexiones es un mecanismo que mantiene un conjunto de conexiones activas a la base de datos, reutilizándolas para múltiples operaciones en lugar de crear y destruir conexiones constantemente. Esta técnica es esencial para aplicaciones Express que manejan múltiples usuarios concurrentes, ya que mejora significativamente el rendimiento y reduce la sobrecarga del servidor.

Ventajas del pool de conexiones

El uso de un pool de conexiones ofrece beneficios importantes sobre las conexiones individuales. Reduce el tiempo de respuesta al eliminar la latencia de establecimiento de conexión, permite manejar múltiples peticiones simultáneas de forma eficiente, y controla el número máximo de conexiones activas para evitar sobrecargar la base de datos.

// services/connectionPool.js
import mysql from 'mysql2/promise';
import dbConfig from '../config/database.js';

// Configuración del pool
const poolConfig = {
  ...dbConfig,
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0,
  acquireTimeout: 60000,
  reconnect: true
};

const pool = mysql.createPool(poolConfig);

export default pool;

Configuración avanzada del pool

La configuración del pool requiere ajustar varios parámetros según las necesidades de la aplicación. El connectionLimit define cuántas conexiones simultáneas puede mantener el pool, mientras que queueLimit controla cuántas peticiones pueden esperar cuando todas las conexiones están ocupadas:

// config/poolConfig.js
const poolConfig = {
  ...dbConfig,
  
  // Límites de conexiones
  connectionLimit: process.env.DB_POOL_SIZE || 10,
  queueLimit: process.env.DB_QUEUE_LIMIT || 0,
  
  // Timeouts
  acquireTimeout: 60000,
  timeout: 60000,
  
  // Reconexión automática
  reconnect: true,
  reconnectDelay: 2000,
  
  // Configuraciones de idle
  idleTimeout: 300000,
  maxIdle: 5,
  
  // Configuraciones de validación
  enableKeepAlive: true,
  keepAliveInitialDelay: 0
};

export default poolConfig;

Implementación del servicio de pool

Un servicio centralizado para el pool facilita su uso a través de toda la aplicación Express. Este servicio encapsula las operaciones comunes y proporciona métodos convenientes para ejecutar consultas:

// services/poolService.js
import mysql from 'mysql2/promise';
import poolConfig from '../config/poolConfig.js';

class PoolService {
  constructor() {
    this.pool = mysql.createPool(poolConfig);
    this.setupEventListeners();
  }
  
  setupEventListeners() {
    this.pool.on('connection', (connection) => {
      console.log(`Nueva conexión establecida: ${connection.threadId}`);
    });
    
    this.pool.on('error', (error) => {
      console.error('Error en el pool de conexiones:', error);
    });
  }
  
  async query(sql, params = []) {
    try {
      const [results] = await this.pool.execute(sql, params);
      return results;
    } catch (error) {
      console.error('Error en consulta del pool:', error);
      throw error;
    }
  }
  
  async transaction(callback) {
    const connection = await this.pool.getConnection();
    
    try {
      await connection.beginTransaction();
      const result = await callback(connection);
      await connection.commit();
      return result;
    } catch (error) {
      await connection.rollback();
      throw error;
    } finally {
      connection.release();
    }
  }
  
  async getPoolStatus() {
    return {
      totalConnections: this.pool.pool._allConnections.length,
      freeConnections: this.pool.pool._freeConnections.length,
      usedConnections: this.pool.pool._allConnections.length - this.pool.pool._freeConnections.length
    };
  }
  
  async closePool() {
    await this.pool.end();
    console.log('Pool de conexiones cerrado');
  }
}

export default new PoolService();

Uso del pool en controladores Express

La integración del pool en los controladores de Express permite manejar múltiples peticiones de forma eficiente. Cada operación utiliza una conexión del pool automáticamente:

// controllers/userController.js
import PoolService from '../services/poolService.js';

export const getUsers = async (req, res) => {
  try {
    const users = await PoolService.query('SELECT * FROM users WHERE active = ?', [1]);
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: 'Error al obtener usuarios' });
  }
};

export const createUser = async (req, res) => {
  const { name, email } = req.body;
  
  try {
    const result = await PoolService.query(
      'INSERT INTO users (name, email) VALUES (?, ?)',
      [name, email]
    );
    
    res.status(201).json({ 
      id: result.insertId, 
      message: 'Usuario creado exitosamente' 
    });
  } catch (error) {
    res.status(500).json({ error: 'Error al crear usuario' });
  }
};

Manejo de transacciones con pool

Las transacciones requieren usar la misma conexión para todas las operaciones. El pool proporciona métodos específicos para obtener y liberar conexiones dedicadas:

// services/orderService.js
import PoolService from './poolService.js';

export const createOrderWithItems = async (orderData, items) => {
  return await PoolService.transaction(async (connection) => {
    // Crear la orden
    const [orderResult] = await connection.execute(
      'INSERT INTO orders (user_id, total) VALUES (?, ?)',
      [orderData.userId, orderData.total]
    );
    
    const orderId = orderResult.insertId;
    
    // Insertar los items de la orden
    for (const item of items) {
      await connection.execute(
        'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)',
        [orderId, item.productId, item.quantity, item.price]
      );
      
      // Actualizar stock del producto
      await connection.execute(
        'UPDATE products SET stock = stock - ? WHERE id = ?',
        [item.quantity, item.productId]
      );
    }
    
    return { orderId, message: 'Orden creada exitosamente' };
  });
};

Monitoreo y optimización del pool

El monitoreo del pool es crucial para identificar cuellos de botella y optimizar el rendimiento. Implementamos endpoints para supervisar el estado del pool:

// routes/admin.js
import express from 'express';
import PoolService from '../services/poolService.js';

const router = express.Router();

router.get('/pool-status', async (req, res) => {
  try {
    const status = await PoolService.getPoolStatus();
    res.json({
      ...status,
      timestamp: new Date().toISOString(),
      healthy: status.freeConnections > 0
    });
  } catch (error) {
    res.status(500).json({ error: 'Error al obtener estado del pool' });
  }
});

export default router;

Configuración por entorno

Diferentes entornos de ejecución requieren configuraciones específicas del pool. En desarrollo podemos usar menos conexiones, mientras que en producción necesitamos más:

// config/environmentPool.js
const getPoolConfig = () => {
  const baseConfig = {
    ...dbConfig,
    waitForConnections: true,
    reconnect: true
  };
  
  switch (process.env.NODE_ENV) {
    case 'development':
      return {
        ...baseConfig,
        connectionLimit: 5,
        queueLimit: 0,
        idleTimeout: 60000
      };
      
    case 'production':
      return {
        ...baseConfig,
        connectionLimit: 20,
        queueLimit: 10,
        idleTimeout: 300000,
        acquireTimeout: 30000
      };
      
    case 'test':
      return {
        ...baseConfig,
        connectionLimit: 2,
        queueLimit: 0,
        idleTimeout: 10000
      };
      
    default:
      return {
        ...baseConfig,
        connectionLimit: 10,
        queueLimit: 0
      };
  }
};

export default getPoolConfig();

Cierre graceful del pool

El cierre adecuado del pool es importante para evitar conexiones colgadas cuando la aplicación se detiene. Implementamos manejadores de señales para cerrar el pool correctamente:

// app.js
import PoolService from './services/poolService.js';

// Manejo de cierre graceful
const gracefulShutdown = async (signal) => {
  console.log(`Recibida señal ${signal}, cerrando aplicación...`);
  
  try {
    await PoolService.closePool();
    console.log('Pool de conexiones cerrado correctamente');
    process.exit(0);
  } catch (error) {
    console.error('Error al cerrar el pool:', error);
    process.exit(1);
  }
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

El pool de conexiones transforma una aplicación Express básica en una solución robusta capaz de manejar cargas de trabajo significativas. La configuración adecuada del pool, junto con el monitoreo continuo, garantiza un rendimiento óptimo y una experiencia de usuario fluida.

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 MySQL con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.