5-5-MVC-API
MVC sin Router y sin Views separadas (el HTML va directamente en el controlador web, sin EJS). La estructura es simple y mantiene la separación de responsabilidades.
📁 Estructura del proyecto
mvc-simple/
├── app.js
├── models/
│ └── productoModel.js
└── controllers/
├── webController.js
└── apiController.js(No hay carpetas views ni routes)
1. Modelo (models/productoModel.js)
// Base de datos en memoria
let productos = [
{ id: 1, nombre: 'Laptop', precio: 1000 },
{ id: 2, nombre: 'Mouse', precio: 25 },
{ id: 3, nombre: 'Teclado', precio: 50 }
];
// Obtener todos
const getAll = () => productos;
// Obtener por ID
const getById = (id) => productos.find(p => p.id === id);
// Crear nuevo producto (devuelve el producto creado)
const create = (nombre, precio) => {
const nuevoId = productos.length > 0 ? Math.max(...productos.map(p => p.id)) + 1 : 1;
const nuevoProducto = { id: nuevoId, nombre, precio: parseFloat(precio) };
productos.push(nuevoProducto);
return nuevoProducto;
};
module.exports = { getAll, getById, create };2. Controlador Web (controllers/webController.js)
Maneja las peticiones de la interfaz web y genera el HTML directamente (sin archivos de vista).
const model = require('../models/productoModel');
// GET / - Mostrar todos los productos (HTML)
const listar = (req, res) => {
const productos = model.getAll();
let listaHTML = '';
for (let p of productos) {
listaHTML += `
<li>
<strong>${p.nombre}</strong> - $${p.precio}
(<a href="/productos/${p.id}">Ver detalles</a>)
</li>
`;
}
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Productos</title>
<style>body{font-family:Arial;margin:30px;} li{margin:10px 0;}</style>
</head>
<body>
<h1>📦 Lista de productos</h1>
<ul>${listaHTML}</ul>
<hr>
<h2>➕ Agregar producto</h2>
<form action="/productos" method="POST">
<input type="text" name="nombre" placeholder="Nombre" required>
<input type="number" step="1" name="precio" placeholder="Precio" required>
<button type="submit">Crear</button>
</form>
<hr>
<p><strong>🔗 Endpoints API (JSON):</strong> <a href="/api/productos">/api/productos</a></p>
</body>
</html>
`);
};
// GET /productos/:id - Mostrar detalle (HTML)
const mostrarDetalle = (req, res) => {
const id = parseInt(req.params.id);
const producto = model.getById(id);
if (!producto) {
return res.status(404).send(`
<!DOCTYPE html>
<html>
<head><title>Error 404</title></head>
<body>
<h1>404 - Producto no encontrado</h1>
<a href="/">Volver</a>
</body>
</html>
`);
}
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>${producto.nombre}</title>
<style>body{font-family:Arial;margin:30px;}</style>
</head>
<body>
<h1>📄 Detalle del producto</h1>
<p><strong>ID:</strong> ${producto.id}</p>
<p><strong>Nombre:</strong> ${producto.nombre}</p>
<p><strong>Precio:</strong> $${producto.precio}</p>
<a href="/">← Volver al listado</a>
</body>
</html>
`);
};
// POST /productos - Crear producto desde formulario (redirige)
const crear = (req, res) => {
const { nombre, precio } = req.body;
if (!nombre || !precio) {
return res.status(400).send(`
<!DOCTYPE html>
<html>
<head><title>Error</title></head>
<body>
<h1>Error: faltan nombre o precio</h1>
<a href="/">Volver</a>
</body>
</html>
`);
}
model.create(nombre, precio);
res.redirect('/');
};
module.exports = { listar, mostrarDetalle, crear };3. Controlador API (controllers/apiController.js)
Maneja las peticiones REST y responde con JSON.
const model = require('../models/productoModel');
// GET /api/productos - Obtener todos (JSON)
const obtenerTodos = (req, res) => {
const productos = model.getAll();
res.json(productos);
};
// GET /api/productos/:id - Obtener uno (JSON)
const obtenerUno = (req, res) => {
const id = parseInt(req.params.id);
const producto = model.getById(id);
if (!producto) {
return res.status(404).json({ error: 'Producto no encontrado' });
}
res.json(producto);
};
// POST /api/productos - Crear producto (JSON)
const crear = (req, res) => {
const { nombre, precio } = req.body;
if (!nombre || typeof precio !== 'number') {
return res.status(400).json({ error: 'Se requiere nombre y precio (número)' });
}
const nuevoProducto = model.create(nombre, precio);
res.status(201).json(nuevoProducto);
};
module.exports = { obtenerTodos, obtenerUno, crear };4. Aplicación principal (app.js)
Configura Express, middlewares y define las rutas directamente (sin Router).
const express = require('express');
const app = express();
const PORT = 3000;
// Middlewares
app.use(express.json()); // Para JSON (API)
app.use(express.urlencoded({ extended: true })); // Para formularios web
// Importar controladores
const webController = require('./controllers/webController');
const apiController = require('./controllers/apiController');
// ========== RUTAS WEB (HTML) ==========
app.get('/', webController.listar);
app.get('/productos/:id', webController.mostrarDetalle);
app.post('/productos', webController.crear);
// ========== RUTAS API (JSON) con prefijo /api ==========
app.get('/api/productos', apiController.obtenerTodos);
app.get('/api/productos/:id', apiController.obtenerUno);
app.post('/api/productos', apiController.crear);
// Iniciar servidor
app.listen(PORT, () => {
console.log(`✅ Servidor MVC (sin router, sin views) en http://localhost:${PORT}`);
console.log('🌐 Interfaz web:');
console.log(' - Lista: http://localhost:3000/');
console.log(' - Detalle: http://localhost:3000/productos/1');
console.log('🔌 API JSON:');
console.log(' - GET /api/productos');
console.log(' - GET /api/productos/:id');
console.log(' - POST /api/productos');
});▶️ Cómo ejecutar
Crear las carpetas y archivos con los códigos anteriores.
Instalar Express:
npm init -y npm install expressEjecutar:
node app.jsAbrir
http://localhost:3000para la interfaz web.Probar la API con Postman o navegador:
GET http://localhost:3000/api/productosGET http://localhost:3000/api/productos/2POST http://localhost:3000/api/productoscon JSON{"nombre":"Monitor","precio":200}
✅ Características de esta versión
MVC puro: Modelo, Controladores (web y API), y las "vistas" están dentro del controlador web (HTML embebido).
Sin Router: Las rutas se definen directamente en
app.js.Sin archivos de vista separados: No se usa EJS ni ningún motor de plantillas; el HTML se genera en el controlador.
Fácil de entender: Ideal para alumnos que ya conocen el ejemplo original y quieren ver la misma funcionalidad pero organizada en capas.
Comentarios
Publicar un comentario