7-assets--express.static('public'
7-assets--express.static('public'
¿Qué son los Assets?
Assets (o "activos" en español) son todos los archivos estáticos que componen la interfaz visual y funcional de una aplicación web.
📦 Tipos de Assets
🎯 Características de los Assets
No cambian dinámicamente - Son iguales para todos los usuarios
Se sirven directamente - No requieren procesamiento en el servidor
Se cachean en el navegador - Mejoran el rendimiento
Se organizan en carpetas - CSS, JS, images, fonts, etc.
📁 Estructura Típica de Assets
text
mi-proyecto/
├── public/ ← Raíz de assets
│ ├── css/
│ │ ├── main.css
│ │ ├── reset.css
│ │ └── responsive.css
│ ├── js/
│ │ ├── main.js
│ │ ├── utils.js
│ │ └── vendor/ ← Librerías externas
│ ├── images/
│ │ ├── logo.png
│ │ ├── background.jpg
│ │ └── icons/
│ ├── fonts/
│ │ └── roboto.woff2
│ └── assets/ ← Carpeta genérica
│ └── ...
💻 En Express con express.static
javascript
const express = require('express');
const app = express();
// Servir todos los assets desde 'public'
app.use(express.static('public'));
// Ahora estos archivos son accesibles:
// http://localhost:3000/css/main.css
// http://localhost:3000/js/main.js
// http://localhost:3000/images/logo.png
🌐 En el HTML (Frontend)
html
<!DOCTYPE html>
<html>
<head>
<!-- CSS Assets -->
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/responsive.css">
<!-- Font Asset -->
<link href="/fonts/roboto.woff2" rel="preload">
</head>
<body>
<!-- Image Assets -->
<img src="/images/logo.png" alt="Logo">
<img src="/images/icons/menu.svg" alt="Menú">
<!-- JavaScript Assets -->
<script src="/js/vendor/jquery.js"></script>
<script src="/js/main.js"></script>
</body>
</html>
🔄 Assets vs Archivos Dinámicos
📊 Ejemplo Práctico
javascript
const express = require('express');
const app = express();
// ✅ Assets (estáticos)
app.use('/css', express.static('public/css'));
app.use('/js', express.static('public/js'));
app.use('/img', express.static('public/images'));
// ✅ Archivo dinámico (generado en servidor)
app.get('/user/:id', (req, res) => {
// Esto NO es un asset - se genera para cada usuario
res.json({ id: req.params.id, name: 'Usuario' });
});
app.listen(3000);
🚀 Buenas Prácticas con Assets
Organización clara
text
public/
├── assets/
│ ├── styles/
│ ├── scripts/
│ └── media/
Optimización
Minificar CSS/JS en producción
Comprimir imágenes (WebP, AVIF)
Usar versionado (cache busting)
Headers de caché
javascript
app.use(express.static('public', {
maxAge: '30d', // Assets cacheados por 30 días
immutable: true // No cambiarán
}));
🎯 Resumen
Assets = Archivos estáticos (CSS, JS, imágenes, fuentes)
No cambian con cada request
Se sirven con express.static
Se organizan en carpetas como public/ o assets/
Mejoran rendimiento con caché
En términos simples: todo lo que ves y escuchas en una web que no es generado en el momento son assets
Estructura del proyecto:
mi-app/ ├── app.js ├── views/ │ └── index.ejs └── package.json
app.js
const express = require('express'); const app = express(); const PORT = 3000; // ============ CONFIGURAR MOTOR DE PLANTILLAS ============ app.set('view engine', 'ejs'); app.set('views', './views'); // ============ MIDDLEWARE ============ app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use('/css', express.static('node_modules/bootstrap/dist/css')); app.use('/js', express.static('node_modules/bootstrap/dist/js')); // ============ DATOS DE EJEMPLO ============ let productos = [ { id: 1, nombre: 'Laptop', precio: 1000 }, { id: 2, nombre: 'Mouse', precio: 25 }, { id: 3, nombre: 'Teclado', precio: 50 } ]; // ============ RUTAS CON VISTAS (HTML) ============ // Ruta principal - Muestra todos los productos y formulario app.get('/', (req, res) => { res.render('index', { titulo: 'Mi Tienda', productos: productos }); }); // ============ RUTAS API (JSON) ============ // GET - Obtener todos los productos app.get('/api/productos', (req, res) => { res.json({ success: true, cantidad: productos.length, productos: productos }); }); // GET - Obtener un producto específico app.get('/api/productos/:id', (req, res) => { const id = parseInt(req.params.id); const producto = productos.find(p => p.id === id); if (!producto) { return res.status(404).json({ success: false, error: 'Producto no encontrado' }); } res.json(producto); }); // POST - Crear nuevo producto (API) app.post('/api/productos', (req, res) => { const { nombre, precio } = req.body; if (!nombre || !precio) { return res.status(400).json({ success: false, error: 'Faltan datos: nombre y precio son requeridos' }); } const nuevoProducto = { id: productos.length + 1, nombre: nombre, precio: parseFloat(precio) }; productos.push(nuevoProducto); res.json({ success: true, producto: nuevoProducto, mensaje: 'Producto creado exitosamente' }); }); // ============ RUTAS CON FORMULARIOS ============ // POST - Crear producto desde el formulario HTML app.post('/crear-producto', (req, res) => { const { nombre, precio } = req.body; if (!nombre || !precio) { return res.status(400).send('Error: Faltan datos. <a href="/">Volver</a>'); } const nuevoProducto = { id: productos.length + 1, nombre: nombre, precio: parseFloat(precio) }; productos.push(nuevoProducto); res.redirect('/'); }); // ============ INICIAR SERVIDOR ============ app.listen(PORT, () => { console.log(`✅ Servidor en http://localhost:${PORT}`); console.log(`📱 Página web: http://localhost:${PORT}`); console.log(`🔌 API: http://localhost:${PORT}/api/productos`); });
views/index.ejs
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><%= titulo %></title> <link rel="stylesheet" href="/css/bootstrap.min.css"> </head> <body> <div class="container mt-5"> <h1 class="text-center mb-4">🛒 <%= titulo %></h1> <div class="row"> <!-- Formulario para crear producto --> <div class="col-md-5"> <div class="card"> <div class="card-header bg-primary text-white"> <h4 class="mb-0">📝 Agregar Producto</h4> </div> <div class="card-body"> <form action="/crear-producto" method="POST"> <div class="mb-3"> <label for="nombre" class="form-label">Nombre del producto:</label> <input type="text" class="form-control" id="nombre" name="nombre" required> </div> <div class="mb-3"> <label for="precio" class="form-label">Precio (USD):</label> <input type="number" class="form-control" id="precio" name="precio" step="0.01" required> </div> <button type="submit" class="btn btn-primary w-100">Agregar Producto</button> </form> </div> </div> </div> <!-- Lista de productos --> <div class="col-md-7"> <div class="card"> <div class="card-header bg-success text-white"> <h4 class="mb-0">📦 Productos</h4> </div> <div class="card-body"> <% if (productos.length > 0) { %> <table class="table table-striped table-hover"> <thead> <tr> <th>ID</th> <th>Nombre</th> <th>Precio</th> <th>Estado</th> </tr> </thead> <tbody> <% productos.forEach(producto => { %> <tr> <td><span class="badge bg-secondary">#<%= producto.id %></span></td> <td><strong><%= producto.nombre %></strong></td> <td class="text-success fw-bold">$<%= producto.precio.toFixed(2) %></td> <td><span class="badge bg-success">Disponible</span></td> </tr> <% }) %> </tbody> </table> <div class="alert alert-info mt-3"> <strong>📊 Total:</strong> <%= productos.length %> productos </div> <% } else { %> <div class="alert alert-warning text-center"> No hay productos disponibles. ¡Agrega el primero! </div> <% } %> </div> </div> </div> </div> <!-- Información de la API --> <div class="row mt-4"> <div class="col-12"> <div class="card"> <div class="card-header bg-dark text-white"> <h4 class="mb-0">🔌 Endpoints API</h4> </div> <div class="card-body"> <ul class="list-group"> <li class="list-group-item"> <strong>GET /api/productos</strong> - Ver todos los productos (JSON) <a href="/api/productos" target="_blank" class="btn btn-sm btn-info float-end">Probar</a> </li> <li class="list-group-item"> <strong>GET /api/productos/:id</strong> - Ver un producto específico (JSON) <a href="/api/productos/1" target="_blank" class="btn btn-sm btn-info float-end">Probar ID 1</a> </li> <li class="list-group-item"> <strong>POST /api/productos</strong> - Crear producto via API (JSON) <button class="btn btn-sm btn-secondary float-end" onclick="mostrarEjemplo()">Ver ejemplo</button> </li> </ul> <div id="ejemploApi" class="alert alert-secondary mt-3" style="display:none;"> <strong>💡 Ejemplo con curl:</strong><br> <code>curl -X POST http://localhost:3000/api/productos \<br> -H "Content-Type: application/json" \<br> -d '{"nombre":"Tablet","precio":300}'</code> </div> </div> </div> </div> </div> </div> <script src="/js/bootstrap.bundle.min.js"></script> <script> function mostrarEjemplo() { const ejemplo = document.getElementById('ejemploApi'); if (ejemplo.style.display === 'none') { ejemplo.style.display = 'block'; } else { ejemplo.style.display = 'none'; } } </script> </body> </html>
package.json
{ "name": "mi-app", "version": "1.0.0", "description": "App sencilla con Express y Bootstrap", "main": "app.js", "scripts": { "start": "node app.js", "dev": "nodemon app.js" }, "dependencies": { "express": "^4.18.2", "ejs": "^3.1.9", "bootstrap": "^5.3.0" }, "devDependencies": { "nodemon": "^3.0.1" } }
Instalación y ejecución:
# 1. Crear carpeta del proyecto mkdir mi-app cd mi-app # 2. Crear archivo package.json npm init -y # 3. Instalar dependencias npm install express ejs bootstrap # 4. Instalar nodemon (opcional, para desarrollo) npm install -D nodemon # 5. Crear carpeta views mkdir views # 6. Crear los archivos: # - app.js (copiar el código) # - views/index.ejs (copiar el código) # 7. Ejecutar la aplicación npm start # o con nodemon para desarrollo automático npm run dev
Características de esta versión:
✅ Bootstrap 5 - Estilos modernos sin CSS personalizado
✅ Diseño responsive - Se adapta a móviles y tablets
✅ Formulario para crear productos
✅ Tabla de productos con Bootstrap
✅ API REST completa (GET y POST)
✅ Interfaz limpia y profesional
Comentarios
Publicar un comentario