11.1-VERSIÓN CON MYSQL
¡Excelente tutorial! Aquí tienes la versión ajustada para trabajar con MySQL en lugar de la base de datos simulada:
🗄️ VERSIÓN CON MYSQL
📁 ESTRUCTURA DEL PROYECTO ACTUALIZADA
text
mi-app-mvc-mysql/
├── models/
│ └── Producto.js
├── controllers/
│ └── productoController.js
├── routes/
│ └── productoRoutes.js
├── config/
│ └── database.js
├── .env
├── app.js
└── server.js
📦 DEPENDENCIAS NECESARIAS
bash
npm install express mysql2 dotenv
🗃️ 1️⃣ CONFIGURACIÓN DE BASE DE DATOS
config/database.js (Configuración de MySQL)
javascript
const mysql = require('mysql2');
require('dotenv').config();
// Crear pool de conexiones
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'tienda_mvc',
port: process.env.DB_PORT || 3306,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// Promisify para usar async/await
const promisePool = pool.promise();
module.exports = promisePool;
.env (Variables de entorno)
env
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=tu_contraseña
DB_NAME=tienda_mvc
DB_PORT=3306
PORT=3000
🏗️ 2️⃣ SCRIPT PARA CREAR LA BASE DE DATOS
setup-db.sql (Ejecutar en MySQL)
sql
-- Crear base de datos
CREATE DATABASE IF NOT EXISTS tienda_mvc;
USE tienda_mvc;
-- Crear tabla de productos
CREATE TABLE IF NOT EXISTS productos (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insertar datos de ejemplo
INSERT INTO productos (nombre, precio) VALUES
('Laptop', 1000.00),
('Mouse', 25.00),
('Teclado', 50.00);
📄 3️⃣ MODELO ACTUALIZADO (con MySQL)
models/Producto.js
javascript
const db = require('../config/database');
class Producto {
// Obtener todos los productos
async obtenerTodos() {
try {
const [rows] = await db.query('SELECT * FROM productos ORDER BY id');
return rows;
} catch (error) {
throw new Error(`Error al obtener productos: ${error.message}`);
}
}
// Obtener un producto por ID
async obtenerPorId(id) {
try {
const [rows] = await db.query('SELECT * FROM productos WHERE id = ?', [id]);
return rows[0] || null;
} catch (error) {
throw new Error(`Error al obtener producto: ${error.message}`);
}
}
// Crear nuevo producto
async crear(nombre, precio) {
try {
const [result] = await db.query(
'INSERT INTO productos (nombre, precio) VALUES (?, ?)',
[nombre, precio]
);
// Retornar el producto creado
return await this.obtenerPorId(result.insertId);
} catch (error) {
throw new Error(`Error al crear producto: ${error.message}`);
}
}
// Actualizar producto
async actualizar(id, nombre, precio) {
try {
const [result] = await db.query(
'UPDATE productos SET nombre = ?, precio = ? WHERE id = ?',
[nombre, precio, id]
);
if (result.affectedRows === 0) return null;
// Retornar el producto actualizado
return await this.obtenerPorId(id);
} catch (error) {
throw new Error(`Error al actualizar producto: ${error.message}`);
}
}
// Eliminar producto
async eliminar(id) {
try {
// Primero obtener el producto para retornarlo
const producto = await this.obtenerPorId(id);
if (!producto) return null;
const [result] = await db.query('DELETE FROM productos WHERE id = ?', [id]);
if (result.affectedRows === 0) return null;
return producto;
} catch (error) {
throw new Error(`Error al eliminar producto: ${error.message}`);
}
}
// Método adicional: buscar por nombre
async buscarPorNombre(nombre) {
try {
const [rows] = await db.query(
'SELECT * FROM productos WHERE nombre LIKE ?',
[`%${nombre}%`]
);
return rows;
} catch (error) {
throw new Error(`Error en la búsqueda: ${error.message}`);
}
}
}
module.exports = new Producto();
🎮 4️⃣ CONTROLADOR ACTUALIZADO (con async/await)
controllers/productoController.js
javascript
const Producto = require('../models/Producto');
class ProductoController {
// GET /productos - Obtener todos
async obtenerTodos(req, res) {
try {
const productos = await Producto.obtenerTodos();
res.json({
success: true,
total: productos.length,
productos: productos
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error interno del servidor',
mensaje: error.message
});
}
}
// POST /productos - Crear nuevo
async crear(req, res) {
try {
const { nombre, precio } = req.body;
// Validaciones
if (!nombre || !precio) {
return res.status(400).json({
success: false,
error: 'Faltan datos: nombre y precio son requeridos'
});
}
if (isNaN(precio) || precio <= 0) {
return res.status(400).json({
success: false,
error: 'El precio debe ser un número positivo'
});
}
if (nombre.length < 2) {
return res.status(400).json({
success: false,
error: 'El nombre debe tener al menos 2 caracteres'
});
}
const nuevoProducto = await Producto.crear(nombre, precio);
res.status(201).json({
success: true,
mensaje: '✅ Producto creado exitosamente',
producto: nuevoProducto
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al crear producto',
mensaje: error.message
});
}
}
// GET /productos/:id - Obtener uno
async obtenerUno(req, res) {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) {
return res.status(400).json({
success: false,
error: 'ID inválido'
});
}
const producto = await Producto.obtenerPorId(id);
if (!producto) {
return res.status(404).json({
success: false,
error: `Producto con ID ${id} no encontrado`
});
}
res.json({
success: true,
producto: producto
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al obtener producto',
mensaje: error.message
});
}
}
// PUT /productos/:id - Actualizar
async actualizar(req, res) {
try {
const id = parseInt(req.params.id);
const { nombre, precio } = req.body;
if (isNaN(id)) {
return res.status(400).json({
success: false,
error: 'ID inválido'
});
}
if (!nombre || !precio) {
return res.status(400).json({
success: false,
error: 'Faltan datos: nombre y precio son requeridos'
});
}
const productoActualizado = await Producto.actualizar(id, nombre, precio);
if (!productoActualizado) {
return res.status(404).json({
success: false,
error: `Producto con ID ${id} no encontrado`
});
}
res.json({
success: true,
mensaje: '✅ Producto actualizado exitosamente',
producto: productoActualizado
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al actualizar producto',
mensaje: error.message
});
}
}
// DELETE /productos/:id - Eliminar
async eliminar(req, res) {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) {
return res.status(400).json({
success: false,
error: 'ID inválido'
});
}
const productoEliminado = await Producto.eliminar(id);
if (!productoEliminado) {
return res.status(404).json({
success: false,
error: `Producto con ID ${id} no encontrado`
});
}
res.json({
success: true,
mensaje: '✅ Producto eliminado exitosamente',
producto: productoEliminado
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error al eliminar producto',
mensaje: error.message
});
}
}
// GET /productos/buscar?nombre=xxx - Búsqueda
async buscar(req, res) {
try {
const { nombre } = req.query;
if (!nombre) {
return res.status(400).json({
success: false,
error: 'Parámetro nombre requerido'
});
}
const productos = await Producto.buscarPorNombre(nombre);
res.json({
success: true,
total: productos.length,
productos: productos
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Error en la búsqueda',
mensaje: error.message
});
}
}
}
module.exports = new ProductoController();
🛣️ 5️⃣ RUTAS ACTUALIZADAS
routes/productoRoutes.js
javascript
const express = require('express');
const productoController = require('../controllers/productoController');
const router = express.Router();
// ============ USANDO app.route() ============
// Rutas para colección de productos
router.route('/productos')
.get(productoController.obtenerTodos) // GET /productos
.post(productoController.crear); // POST /productos
// Rutas para producto específico
router.route('/productos/:id')
.get(productoController.obtenerUno) // GET /productos/:id
.put(productoController.actualizar) // PUT /productos/:id
.delete(productoController.eliminar); // DELETE /productos/:id
// Ruta adicional para búsqueda
router.route('/productos/buscar')
.get(productoController.buscar); // GET /productos/buscar?nombre=laptop
module.exports = router;
⚙️ 6️⃣ CONFIGURACIÓN PRINCIPAL
app.js
javascript
const express = require('express');
const app = express();
const productoRoutes = require('./routes/productoRoutes');
require('dotenv').config();
// Middlewares globales
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Archivos estáticos (opcional)
app.use(express.static('public'));
// ============ REGISTRAR RUTAS ============
app.use('/', productoRoutes);
// Ruta de bienvenida (opcional)
app.get('/', (req, res) => {
res.send(`
<h1>📦 API MVC con MySQL y app.route()</h1>
<h2>Endpoints disponibles:</h2>
<ul>
<li><strong>GET</strong> /productos - Ver todos</li>
<li><strong>GET</strong> /productos/1 - Ver uno</li>
<li><strong>GET</strong> /productos/buscar?nombre=laptop - Buscar</li>
<li><strong>POST</strong> /productos - Crear</li>
<li><strong>PUT</strong> /productos/1 - Actualizar</li>
<li><strong>DELETE</strong> /productos/1 - Eliminar</li>
</ul>
<h3>Ejemplo POST/PUT (JSON):</h3>
<code>{"nombre": "Monitor", "precio": 300}</code>
<hr/>
<h3>💾 Base de datos MySQL:</h3>
<p>Base de datos: <strong>tienda_mvc</strong></p>
<p>Tabla: <strong>productos</strong></p>
`);
});
// Manejo de errores 404
app.use((req, res) => {
res.status(404).json({
success: false,
error: 'Ruta no encontrada'
});
});
module.exports = app;
server.js
javascript
const app = require('./app');
const PORT = process.env.PORT || 3000;
// Verificar conexión a la base de datos al iniciar
const db = require('./config/database');
async function verificarDB() {
try {
const [result] = await db.query('SELECT 1');
console.log('✅ Conexión a MySQL establecida correctamente');
// Opcional: Mostrar versión de MySQL
const [version] = await db.query('SELECT VERSION() as version');
console.log(`📀 MySQL Versión: ${version[0].version}`);
} catch (error) {
console.error('❌ Error al conectar a MySQL:', error.message);
console.error('Verifica que:');
console.error(' 1. MySQL esté instalado y ejecutándose');
console.error(' 2. Las credenciales en .env sean correctas');
console.error(' 3. La base de datos "tienda_mvc" exista');
process.exit(1);
}
}
// Iniciar servidor después de verificar DB
async function startServer() {
await verificarDB();
app.listen(PORT, () => {
console.log(`\n=================================`);
console.log(`🚀 Servidor MVC con MySQL corriendo en http://localhost:${PORT}`);
console.log(`📦 API de Productos con app.route()`);
console.log(`💾 Base de datos: MySQL`);
console.log(`\n📌 Probar endpoints:`);
console.log(` GET /productos`);
console.log(` GET /productos/1`);
console.log(` GET /productos/buscar?nombre=tecla`);
console.log(` POST /productos`);
console.log(` PUT /productos/1`);
console.log(` DELETE /productos/1`);
console.log(`=================================\n`);
});
}
startServer();
🚀 CÓMO EJECUTAR
bash
# 1. Instalar dependencias
npm install express mysql2 dotenv
# 2. Crear la base de datos (ejecutar en MySQL)
mysql -u root -p < setup-db.sql
# 3. Configurar .env con tus credenciales
# Editar archivo .env con tu contraseña de MySQL
# 4. Ejecutar el servidor
node server.js
🧪 PROBAR CON CURL
bash
# GET todos los productos
curl http://localhost:3000/productos
# GET producto específico
curl http://localhost:3000/productos/1
# GET búsqueda por nombre
curl "http://localhost:3000/productos/buscar?nombre=laptop"
# POST crear producto
curl -X POST http://localhost:3000/productos \
-H "Content-Type: application/json" \
-d '{"nombre":"Monitor 4K","precio":350.00}'
# PUT actualizar producto
curl -X PUT http://localhost:3000/productos/1 \
-H "Content-Type: application/json" \
-d '{"nombre":"Ultrabook Pro","precio":1500.00}'
# DELETE eliminar producto
curl -X DELETE http://localhost:3000/productos/2
🔄 DIFERENCIAS PRINCIPALES
💡 VENTAJAS DE USAR MySQL
✅ Persistencia de datos - No se pierden al reiniciar
✅ Consultas complejas - JOIN, filtros avanzados, agregaciones
✅ Integridad referencial - Relaciones entre tablas
✅ Transacciones ACID - Operaciones seguras
✅ Escalable - Maneja grandes volúmenes de datos
✅ Concurrencia - Múltiples usuarios simultáneos
¡Ahora tienes un CRUD completo usando MySQL con arquitectura MVC y app.route()
Comentarios
Publicar un comentario