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ícateConfiguració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.
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.
Introducción A Expressjs
Introducción Y Entorno
Instalación De Express
Introducción Y Entorno
Estados Http
Routing
Métodos Delete
Routing
Parámetros Y Query Strings
Routing
Métodos Get
Routing
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.