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

Aspecto

Versión original

Versión MySQL

Datos

Arreglo en memoria

Base de datos persistente

Operaciones

Sincrónicas

Asíncronas (async/await)

Persistencia

Se pierde al reiniciar

Se mantiene en disco

Consultas

Métodos de array

SQL queries

Escalabilidad

Limitada a memoria

Escala con la DB

Concurrencia

No maneja

Manejo nativo de MySQL

💡 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

Entradas más populares de este blog

Cómo Iniciar un Proyecto Node.js

6-Middleware?

5-Express--Curso de Node.js [ #05 Introducción a Express.js ]