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:
// ANTES (Parte 4)
const router = express.Router();
module.exports = router;a esto:
// 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:
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.
app.locals.entries = []; // ✅ Ahora es globalCualquier 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)
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.json4️⃣ El nuevo routes/index.js (paso a paso)
Paso 1: Exportar una funci贸n que recibe app
// routes/index.js
module.exports = (app) => {
// Aqu铆 dentro tenemos acceso a 'app'
};Paso 2: Crear el arreglo y guardarlo en app.locals
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)
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):
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:
require('./routes/index')(app);Esto hace dos cosas:
require('./routes/index')→ Importa la funci贸n que exportamos(app)→ Ejecuta esa funci贸n pas谩ndoleappcomo argumento
6️⃣ Actualizar index.ejs para mostrar si hay entradas
Basado en tu 煤ltima imagen, actualiza views/index.ejs:
<!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
<!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
| Aspecto | ANTES (Parte 4) | AHORA (Parte 4.1) |
|---|---|---|
| Exportaci贸n | module.exports = router | module.exports = (app) => { ... } |
| Rutas | router.get() | app.get() |
| Datos | Variable local entries | app.locals.entries (global) |
| app.js | app.use('/', require('./routes/index')) | require('./routes/index')(app) |
| Acceso en vistas | Solo si pasabas entries manualmente | Disponible autom谩ticamente como entries |
9️⃣ Explicaci贸n de app.locals (IMPORTANTE)
// 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:
npm run devFlujo de prueba:
Abre
http://localhost:3000/Ver谩s "No hay entradas a煤n"
Haz click en "Write a GuestBook"
Llena el formulario:
Title: "Mi primera entrada"
Entry Text: "Hola mundo"
Enviar
Ser谩s redirigido al inicio
Ver谩s "Existen entradas" y tu mensaje
馃摑 C贸digo final completo
routes/index.js (COMPLETO Y CORREGIDO):
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):
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 pregunta | Respuesta 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
Publicar un comentario