8-Template Engines
¿Qué es un Motor de Plantillas?
tu-proyecto/ ├── public/ # Carpeta para archivos estáticos │ ├── css/ │ │ └── styles.css │ ├── js/ │ │ └── script.js │ └── images/ │ └── logo.png ├── index.ejs # Plantilla EJS (en raíz) ├── app.js # Servidor Express └── package.json # Dependencias
Un motor de plantillas es una herramienta que permite generar contenido HTML (u otros formatos) de manera dinámica, insertando datos en estructuras predefinidas. Esto facilita:
pp.set('view engine', 'ejs');
app.set('views', './templates');
res.render('index'); // Buscará /home/usuario/proyecto/index.ejsParámetros:
Primer parámetro (
'view engine'):Es una clave de configuración predefinida por Express.
Su único propósito es indicar a Express qué motor de plantillas usar por defecto cuando se llama a
res.render().No es modificable — siempre debe ser el string
'view engine'.
Segundo parámetro (
'ejs'):Es el nombre del paquete del motor de plantillas instalado (en este caso, el paquete
ejs).Configura Express para usar EJS como motor de plantillas. Esto significa que:
Los archivos con extensión
.ejsse procesarán automáticamente.Podrás usar sintaxis EJS (
<% %>,<%= %>, etc.) en tus plantillas.Cuando llames
res.render('archivo'), Express buscaráarchivo.ejs.
Función: Establece configuraciones en Express.
Primer parámetro (
'views'):Es una clave predefinida de Express (como
'view engine').Indica que estás configurando la ruta de la carpeta de plantillas.
¡Debe ser exactamente
'views'! (Express lo espera así).
Segundo parámetro (
path.join(__dirname)):Define la ruta absoluta donde están las plantillas.
Usa
path.join()y__dirnamepara garantizar compatibilidad entre sistemas operativos (Windows/macOS/Linux).
La carpeta no tiene que llamarse obligatoriamente "views" (aunque es el nombre por defecto). Puedes usar cualquier nombre que quieras.
Opción 1: Usar la carpeta "views" (por defecto)
app.set('view engine', 'ejs');
Qué hace: Le dice a Express: "Usa EJS para generar el HTML"
Dónde buscar: Buscará archivos .ejs en la carpeta views/ (por defecto)
Si tu carpeta se llama "views" (el nombre estándar), puedes omitir la segunda línea porque Express ya la busca automáticamente:
// Solo necesitas esto si la carpeta se llama "views"
app.set('view engine', 'ejs');
// No necesitas app.set('views', ...) porque Express ya usa "views" por defectoEstructura con views/ :
mi-proyecto/
├── app.js
├── package.json
└── views/ ← Carpeta creada
├── index.ejs ← Plantilla dentro de views/
└── about.ejs ← Otra plantilla dentro de views/
mi-proyecto/
├── app.js
├── package.json
└── views/ ← Carpeta creada
├── index.ejs ← Plantilla dentro de views/
└── about.ejs ← Otra plantilla dentro de views/Opción 2: Usar una carpeta con nombre diferente (ej: "templates")
app.set('views', './templates');
Qué hace: Le dice a Express: "No uses la carpeta views, usa templates en su lugar"
Por qué: Cambias la ubicación donde Express buscará los archivos .ejs
Si tu carpeta se llama "templates", necesitas especificarlo:
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'templates')); // Carpeta personalizadaEstructura con templates/ :
mi-proyecto/
├── app.js
├── package.json
└── templates/ ← Carpeta con nombre personalizado
├── index.ejs ← Plantilla dentro de templates/
└── about.ejs ← Otra plantilla dentro de templates/
mi-proyecto/
├── app.js
├── package.json
└── templates/ ← Carpeta con nombre personalizado
├── index.ejs ← Plantilla dentro de templates/
└── about.ejs ← Otra plantilla dentro de templates/Opción 3: Mantener todo en la raíz (sin subcarpeta)
app.set('view engine', 'ejs');
app.set('views', __dirname); // Busca .ejs en la misma carpeta que app.js
app.set('view engine', 'ejs');
app.set('views', __dirname); // Busca .ejs en la misma carpeta que app.jsEstructura resultante:
mi-proyecto/
├── app.js
├── index.ejs ← Plantilla en la raíz
├── about.ejs ← Otra plantilla en la raíz
└── package.jsonUsar path.join(__dirname) es equivalente:
app.set('views', path.join(__dirname)); // Mismo resultado que __dirnameLa diferencia clave: 'views' vs. el nombre real de la carpeta
Cuando usas:
app.set('views', path.join(__dirname));'views' es solo el nombre de la configuración en Express (no el nombre de la carpeta).
path.join(__dirname) define que las plantillas están directamente en la raíz del proyecto (sin carpeta views).
app.set('views', path.join(__dirname));'views' es solo el nombre de la configuración en Express (no el nombre de la carpeta).
path.join(__dirname) define que las plantillas están directamente en la raíz del proyecto (sin carpeta views).
Opción 4: Usar la carpeta "templates" sin path.join
También puedes usar una ruta relativa (menos recomendada pero válida):
app.set('view engine', 'ejs');
app.set('views', './templates'); // Ruta relativa
Usar path.join es más robusto y seguro, especialmente en diferentes sistemas operativos. Usar strings simples como './templates' puede fallar en ciertos casos.
app.set('view engine', 'ejs'); app.set('views', './templates'); // Ruta relativa
path.join es más robusto y seguro, especialmente en diferentes sistemas operativos. Usar strings simples como './templates' puede fallar en ciertos casos.La diferencia práctica:
Usando string simple (peligroso):
app.set('views', './templates'); // ⚠️ Puede fallarUsando path.join (recomendado):
app.set('views', path.join(__dirname, 'templates')); // ✅ Siempre funciona¿Por qué path.join es mejor?
1. Problemas con rutas relativas
Imagina que ejecutas tu app desde diferentes lugares:
# Ejecutas desde la carpeta del proyecto (funciona)
cd /mi-proyecto
node app.js # ✅ './templates' funciona
# Ejecutas desde otra carpeta (FALLA)
cd /home/usuario
node /mi-proyecto/app.js # ❌ './templates' busca en /home/usuario/templates__dirname siempre da la ruta absoluta de la carpeta donde está el archivo actual
const express = require('express');
const app = express();
const path = require('path');
// Configurar EJS (plantillas en raíz)
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname)); // Busca plantillas en el directorio actual
// Servir archivos estáticos desde /public
app.use(express.static(path.join(__dirname, 'public')));
// Ruta principal
app.get('/', (req, res) => {
res.render('index', { nombreUsuario: 'Juan' });
});
app.listen(3000, () => {
console.log('Servidor en http://localhost:3000');
});convertido a EJS (Opción 1: carpeta views)
1. Nueva estructura de archivos:
mi-proyecto/
├── app.js
├── package.json
├── public/
│ └── (archivos estáticos opcionales)
└── views/ ← Carpeta creada
├── index.ejs ← Vista principal
└── productos.ejs ← Vista de productos (opcional)
mi-proyecto/
├── app.js
├── package.json
├── public/
│ └── (archivos estáticos opcionales)
└── views/ ← Carpeta creada
├── index.ejs ← Vista principal
└── productos.ejs ← Vista de productos (opcional)2. Código actualizado (app.js):
const express = require('express');
const path = require('path'); // ← Importante para path.join
const app = express();
const PORT = 3000;
// ============ CONFIGURAR EJS ============
app.set('view engine', 'ejs'); // Opción 1: carpeta 'views' por defecto
// No necesitas app.set('views') porque Express busca en 'views/'
// ============ MIDDLEWARE STATIC ============
app.use(express.static('public'));
app.use('/css', express.static('node_modules/bootstrap/dist/css'));
app.use('/js', express.static('node_modules/bootstrap/dist/js'));
// Datos de ejemplo
const productos = [
{ id: 1, nombre: 'Laptop', precio: 1000 },
{ id: 2, nombre: 'Mouse', precio: 25 },
{ id: 3, nombre: 'Teclado', precio: 50 }
];
// ============ RUTAS ============
// API REST (sigue igual)
app.get('/productos', (req, res) => {
res.json({
success: true,
cantidad: productos.length,
productos: productos
});
});
app.get('/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({ error: 'Producto no encontrado' });
}
res.json(producto);
});
// Ruta principal - AHORA RENDERIZA EJS
app.get('/', (req, res) => {
res.render('index', { productos }); // ← Pasa datos a la vista
});
app.listen(PORT, () => {
console.log(`🚀 Servidor corriendo en http://localhost:${PORT}`);
console.log(`📦 Ver productos: http://localhost:${PORT}/productos`);
console.log(`🎨 Ver interfaz: http://localhost:${PORT}`);
});
const express = require('express');
const path = require('path'); // ← Importante para path.join
const app = express();
const PORT = 3000;
// ============ CONFIGURAR EJS ============
app.set('view engine', 'ejs'); // Opción 1: carpeta 'views' por defecto
// No necesitas app.set('views') porque Express busca en 'views/'
// ============ MIDDLEWARE STATIC ============
app.use(express.static('public'));
app.use('/css', express.static('node_modules/bootstrap/dist/css'));
app.use('/js', express.static('node_modules/bootstrap/dist/js'));
// Datos de ejemplo
const productos = [
{ id: 1, nombre: 'Laptop', precio: 1000 },
{ id: 2, nombre: 'Mouse', precio: 25 },
{ id: 3, nombre: 'Teclado', precio: 50 }
];
// ============ RUTAS ============
// API REST (sigue igual)
app.get('/productos', (req, res) => {
res.json({
success: true,
cantidad: productos.length,
productos: productos
});
});
app.get('/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({ error: 'Producto no encontrado' });
}
res.json(producto);
});
// Ruta principal - AHORA RENDERIZA EJS
app.get('/', (req, res) => {
res.render('index', { productos }); // ← Pasa datos a la vista
});
app.listen(PORT, () => {
console.log(`🚀 Servidor corriendo en http://localhost:${PORT}`);
console.log(`📦 Ver productos: http://localhost:${PORT}/productos`);
console.log(`🎨 Ver interfaz: http://localhost:${PORT}`);
});3. Vista EJS (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>Mi Tienda con EJS</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center">📦 Mi Tienda de Productos</h1>
<div class="row mt-4">
<div class="col-md-6 offset-md-3">
<div class="card">
<div class="card-header">
<h3>Productos disponibles</h3>
</div>
<div class="card-body">
<!-- EJS RENDERIZA DIRECTAMENTE LOS PRODUCTOS -->
<ul class="list-group">
<% productos.forEach(producto => { %>
<li class="list-group-item">
<strong><%= producto.nombre %></strong> - $<%= producto.precio %>
<a href="/productos/<%= producto.id %>" class="btn btn-sm btn-primary float-end">Ver</a>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</div>
</div>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi Tienda con EJS</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center">📦 Mi Tienda de Productos</h1>
<div class="row mt-4">
<div class="col-md-6 offset-md-3">
<div class="card">
<div class="card-header">
<h3>Productos disponibles</h3>
</div>
<div class="card-body">
<!-- EJS RENDERIZA DIRECTAMENTE LOS PRODUCTOS -->
<ul class="list-group">
<% productos.forEach(producto => { %>
<li class="list-group-item">
<strong><%= producto.nombre %></strong> - $<%= producto.precio %>
<a href="/productos/<%= producto.id %>" class="btn btn-sm btn-primary float-end">Ver</a>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</div>
</div>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>Diferencias clave con tu código original:
Tu código original Con EJS res.sendFile(__dirname + '/public/index.html')res.render('index', { productos })Los productos se cargaban con fetch() Los productos se inyectan directamente desde el servidor HTML estático en public/ HTML dinámico en views/ Requiere JavaScript en cliente Todo se renderiza en el servidor
| Tu código original | Con EJS |
|---|---|
res.sendFile(__dirname + '/public/index.html') | res.render('index', { productos }) |
| Los productos se cargaban con fetch() | Los productos se inyectan directamente desde el servidor |
HTML estático en public/ | HTML dinámico en views/ |
| Requiere JavaScript en cliente | Todo se renderiza en el servidor |
Ventajas de usar EJS:
SEO mejorado - El HTML ya viene con los datos
Carga más rápida - No esperas fetch() adicional
Código más limpio - Sin JavaScript de cliente para mostrar productos
Falla elegantemente - Si el cliente tiene JS deshabilitado, sigue funcionando
SEO mejorado - El HTML ya viene con los datos
Carga más rápida - No esperas fetch() adicional
Código más limpio - Sin JavaScript de cliente para mostrar productos
Falla elegantemente - Si el cliente tiene JS deshabilitado, sigue funcionando
¿Quieres mantener la versión con fetch?
Si prefieres la versión con fetch (cliente JavaScript), solo cambia la vista:
<!-- views/index.ejs (versión con fetch) -->
<div id="productos"></div>
<script>
fetch('/productos')
.then(response => response.json())
.then(data => {
// mismo código que tenías
});
</script>¿Cuál versión prefieres? ¿Renderizado en servidor (EJS puro) o cliente (fetch)?
Comentarios
Publicar un comentario