4.1: Formularios POST y variable global app.locals

馃幆 Objetivo de esta parte

Entender por qu茅 cambiamos de router a module.exports = (app) => y c贸mo usar app.locals para compartir datos entre rutas.


❓ La duda que tienes (y es muy importante)

Pregunta: ¿Por qu茅 en las im谩genes cambian de esto:

javascript
// ANTES (Parte 4)
const router = express.Router();
module.exports = router;

a esto:

javascript
// AHORA (nuevas im谩genes)
module.exports = (app) => {
    // usamos app directamente
};

Respuesta: Porque app.locals es un objeto global del servidor. Si usamos router, no tenemos acceso a app.locals. Necesitamos pasar app a nuestro archivo de rutas para poder guardar las entradas en un lugar accesible desde cualquier ruta.


1️⃣ El problema con el c贸digo anterior

En la Parte 4 ten铆amos este c贸digo en routes/index.js:

javascript
let entries = [];  // ❌ Esta variable es LOCAL al archivo
router.get('/', (req, res) => { ... });

Problema: La variable entries solo existe dentro de routes/index.js. Si tuvi茅ramos m煤ltiples archivos de rutas, no podr铆an compartir los datos.


2️⃣ La soluci贸n: app.locals

Express tiene un objeto llamado app.locals que es compartido por TODAS las rutas y plantillas.

javascript
app.locals.entries = [];  // ✅ Ahora es global

Cualquier archivo puede acceder a app.locals.entries y cualquier plantilla EJS puede usar entries directamente.


3️⃣ Nueva estructura del proyecto (basada en tus im谩genes)

text
ejs-node/
├── routes/
│   └── index.js      (AHORA exporta una funci贸n)
├── views/
│   ├── partials/
│   │   ├── head.ejs
│   │   └── header.ejs
│   ├── index.ejs
│   └── new-entry.ejs
├── app.js            (AHORA llama a routes pas谩ndole app)
└── package.json

4️⃣ El nuevo routes/index.js (paso a paso)

Paso 1: Exportar una funci贸n que recibe app

javascript
// routes/index.js
module.exports = (app) => {
    // Aqu铆 dentro tenemos acceso a 'app'
};

Paso 2: Crear el arreglo y guardarlo en app.locals

javascript
module.exports = (app) => {
    let entries = [];                    // Arreglo local
    app.locals.entries = entries;        // Hacerlo global
    
    // Ahora cualquier vista puede usar 'entries'
};

Paso 3: Definir las rutas usando app (no router)

javascript
module.exports = (app) => {
    let entries = [];
    app.locals.entries = entries;
    
    // Ruta principal - usa app, no router
    app.get('/', (req, res) => {
        res.render('index', {
            title: 'Inicio'
        });
    });
    
    // Mostrar formulario
    app.get('/new-entry', (req, res) => {
        res.render('new-entry', {
            title: 'Nueva Entrada'
        });
    });
    
    // Procesar formulario (POST)
    app.post('/new-entry', (req, res) => {
        if (!req.body.title || !req.body.body) {
            res.status(400).send('Entradas deben tener un t铆tulo y un cuerpo');
            return;
        }
        
        let newEntry = {
            title: req.body.title,
            content: req.body.body,
            published: new Date()
        };
        
        entries.push(newEntry);
        res.redirect('/');
    });
};

5️⃣ El nuevo app.js (c贸mo se conecta)

Forma correcta (usando require y llamando a la funci贸n):

javascript
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const bodyParser = require('body-parser');

const app = express();

// Configuraciones
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// Middlewares
app.use(morgan('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// IMPORTANTE: Cargar rutas PASANDO 'app' como argumento
require('./routes/index')(app);

// Iniciar servidor
app.listen(app.get('port'), () => {
    console.log(`servidor en puerto ${app.get('port')}`);
});

馃摉 Explicaci贸n de la l铆nea clave:

javascript
require('./routes/index')(app);

Esto hace dos cosas:

  1. require('./routes/index') → Importa la funci贸n que exportamos

  2. (app) → Ejecuta esa funci贸n pas谩ndole app como argumento


6️⃣ Actualizar index.ejs para mostrar si hay entradas

Basado en tu 煤ltima imagen, actualiza views/index.ejs:

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title><%= title %></title>
    <% include partials/head %>
</head>
<body class="container">
    <% include partials/header %>
    
    <div class="jumbotron">
        <h1>Express GuestBook</h1>
        <a href="/new-entry" class="btn btn-primary btn-lg">
            Write a GuestBook
        </a>
    </div>
    
    <!-- Verificar si existen entradas -->
    <% if(entries.length) { %>
        <h1>Existen entradas</h1>
        
        <!-- Mostrar cada entrada -->
        <% for(let i = entries.length - 1; i >= 0; i--) { %>
            <div class="panel panel-default">
                <div class="panel-heading">
                    <strong><%= entries[i].title %></strong>
                    <small class="pull-right">
                        <%= entries[i].published.toLocaleString() %>
                    </small>
                </div>
                <div class="panel-body">
                    <%= entries[i].content %>
                </div>
            </div>
        <% } %>
        
    <% } else { %>
        <div class="alert alert-info">
            No hay entradas a煤n. ¡S茅 el primero en escribir!
        </div>
    <% } %>
</body>
</html>

7️⃣ El formulario completo new-entry.ejs

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title><%= title %></title>
    <% include partials/head %>
</head>
<body class="container">
    <% include partials/header %>
    
    <div class="col-xs-8 col-xs-offset-2">
        <h2 class="text-center">Write a new Entry</h2>
        
        <!-- IMPORTANTE: action="/new-entry" coincide con app.post -->
        <form method="POST" action="/new-entry">
            <div class="form-group">
                <label for="title">Title:</label>
                <input 
                    type="text" 
                    name="title" 
                    id="title" 
                    class="form-control"
                    placeholder="Entry a new Title"
                    required>
            </div>

            <div class="form-group">
                <label for="body">Entry Text:</label>
                <textarea 
                    class="form-control"
                    id="body"
                    name="body"
                    rows="5"
                    required></textarea>
            </div>

            <div class="form-group">
                <input type="submit" value="Post Entry" class="btn btn-primary">
                <a href="/" class="btn btn-default">Cancel</a>
            </div>
        </form>
    </div>
</body>
</html>

8️⃣ Comparativa: ANTES vs AHORA

AspectoANTES (Parte 4)AHORA (Parte 4.1)
Exportaci贸nmodule.exports = routermodule.exports = (app) => { ... }
Rutasrouter.get()app.get()
DatosVariable local entriesapp.locals.entries (global)
app.jsapp.use('/', require('./routes/index'))require('./routes/index')(app)
Acceso en vistasSolo si pasabas entries manualmenteDisponible autom谩ticamente como entries

9️⃣ Explicaci贸n de app.locals (IMPORTANTE)

javascript
// En routes/index.js
app.locals.entries = entries;

// En cualquier vista EJS (index.ejs, new-entry.ejs, etc.)
<% if(entries.length) { %>  ← entries viene de app.locals autom谩ticamente

¿Qu茅 hace app.locals?

  • Es un objeto global del servidor Express

  • Todo lo que guardes ah铆 est谩 disponible en TODAS las vistas

  • No necesitas pasar datos manualmente con res.render('index', { entries: entries })


馃敓 Probar la aplicaci贸n

Iniciar servidor:

bash
npm run dev

Flujo de prueba:

  1. Abre http://localhost:3000/

  2. Ver谩s "No hay entradas a煤n"

  3. Haz click en "Write a GuestBook"

  4. Llena el formulario:

    • Title: "Mi primera entrada"

    • Entry Text: "Hola mundo"

  5. Enviar

  6. Ser谩s redirigido al inicio

  7. Ver谩s "Existen entradas" y tu mensaje


馃摑 C贸digo final completo

routes/index.js (COMPLETO Y CORREGIDO):

javascript
module.exports = (app) => {
    let entries = [];
    app.locals.entries = entries;

    app.get('/', (req, res) => {
        res.render('index', {
            title: 'Inicio'
        });
    });

    app.get('/new-entry', (req, res) => {
        res.render('new-entry', {
            title: 'Nueva Entrada'
        });
    });

    app.post('/new-entry', (req, res) => {
        if (!req.body.title || !req.body.body) {
            res.status(400).send('Entradas deben tener un t铆tulo y un cuerpo');
            return;
        }

        let newEntry = {
            title: req.body.title,
            content: req.body.body,
            published: new Date()
        };

        entries.push(newEntry);
        res.redirect('/');
    });
};

app.js (COMPLETO):

javascript
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const bodyParser = require('body-parser');

const app = express();

app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(morgan('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Cargar rutas - PASAMOS app como argumento
require('./routes/index')(app);

app.listen(app.get('port'), () => {
    console.log(`servidor en puerto ${app.get('port')}`);
});

✅ Resumen para entenderlo bien

Tu preguntaRespuesta clara
¿Por qu茅 module.exports = (app) =>?Para poder usar app.locals y compartir datos entre todas las rutas
¿Qu茅 es app.locals?Un objeto global donde guardas datos accesibles desde cualquier vista
¿Por qu茅 no usar router?router no tiene acceso a app.locals directamente
¿C贸mo accedo a entries en las vistas?Autom谩ticamente, solo escribe <%= entries.length %>
¿Qu茅 hace require('./routes/index')(app)?Importa la funci贸n y la ejecuta pas谩ndole app

馃殌 ¿Qu茅 sigue?

Ahora que entiendes app.locals, la pr贸xima parte podr铆a incluir:

  • Mensajes flash (notificaciones al usuario)

  • Persistencia (guardar en archivo JSON)

  • Editar/Eliminar entradas

¿Qued贸 clara la explicaci贸n? ¿Tienes m谩s dudas sobre app.locals o el cambio de router a app?

Comentarios

Entradas m谩s populares de este blog

C贸mo Iniciar un Proyecto Node.js

9-Nuestra Primera Plantilla con EJS

Lista de tareas-routing y plantillas