8-Template Engines

¿Qué es un Motor de Plantillas?

  5-EXPRESS
           |
          V
           |
          V
 7-express.static
           |
          V
8-Motor de plantilla

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.ejs

Parámetros:

  1. 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'.

  2. Segundo parámetro ('ejs'):

    • Es el nombre del paquete del motor de plantillas instalado (en este caso, el paquete ejs).

  3. Configura Express para usar EJS como motor de plantillas. Esto significa que:

    • Los archivos con extensión .ejs se 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 __dirname para 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:

javascript
// 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 defecto

Estructura con views/ :

text
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:

javascript
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'templates')); // Carpeta personalizada

Estructura con templates/ :

text
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)

javascript
app.set('view engine', 'ejs');
app.set('views', __dirname); // Busca .ejs en la misma carpeta que app.js

Estructura resultante:

text
mi-proyecto/
├── app.js
├── index.ejs      ← Plantilla en la raíz
├── about.ejs      ← Otra plantilla en la raíz
└── package.json

Usar path.join(__dirname) es equivalente:

javascript
app.set('views', path.join(__dirname)); // Mismo resultado que __dirname

La diferencia clave: 'views' vs. el nombre real de la carpeta

Cuando usas:

javascript
Copy
Download
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):

javascript
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.

La diferencia práctica:

Usando string simple (peligroso):

javascript
app.set('views', './templates'); // ⚠️ Puede fallar

Usando path.join (recomendado):

javascript
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:

bash
# 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:

text
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):

javascript
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):

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 originalCon 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 clienteTodo se renderiza en el servidor

Ventajas de usar EJS:

  1. SEO mejorado - El HTML ya viene con los datos

  2. Carga más rápida - No esperas fetch() adicional

  3. Código más limpio - Sin JavaScript de cliente para mostrar productos

  4. 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:

ejs
<!-- 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

Entradas más populares de este blog

Cómo Iniciar un Proyecto Node.js

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