Receta del día
Parte de la sección Conceptos básicos de Express del Journey de Node.js de Coddy. Lección 19 de 20.
Recipe Box recibe una receta del día: la misma receta para todos durante todo el día y otra diferente mañana. La fecha la elige, y ?date= te permite probar cualquier día.
new Date('2026-03-01') corresponde a la medianoche UTC de ese día. Su día del año se cuenta desde el 31 de diciembre, así que el 1 de enero es el día 1: (date - Date.UTC(year, 0, 0)) / 86400000, ya que un día tiene 86 400 000 milisegundos. Un texto que no es una fecha da como resultado una fecha no válida, cuyo getTime() es NaN.
Añade la ruta en server.js, antes del controlador 404. Elige entre las recetas de data/recipes.json, como hace la API, para que una receta que alguien publique pueda ser la receta del día: exporta loadRecipes desde routes/recipes.js y espera a que se complete.
Desafío
FácilGET /api/today?date=2026-03-01responde conrecipes[day % recipes.length]en formato JSON;recipesproviene deloadRecipes()ydayrepresenta el día del año.- Sin
date: hoy. - Una fecha que no se puede analizar:
400con{ "error": "Use a date like 2026-03-01" }.
Envía ?date=2026-03-01 dos veces y después ?date=soon.
Pruébalo tú mismo
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.js';
import recipesRouter from './routes/recipes.js';
import reviewsRouter from './routes/reviews.js';
const app = express();
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
app.use((req, res, next) => {
res.set('X-Request-Id', randomUUID());
next();
});
app.use(express.json());
app.use(express.static('public'));
const escapeHtml = (text) =>
text
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"');
app.get('/', (req, res) => {
const items = recipes.map((r) => `<li><a href="/recipes/${r.id}">${escapeHtml(r.title)}</a></li>`);
res.send(`<link rel="stylesheet" href="/style.css"><h1>Recipe Box</h1><ul>${items.join('')}</ul>`);
});
// type('text') envía texto sin formato, por lo que cada título aparece en su propia línea.
app.get('/recipes', (req, res) => {
const { tag } = req.query;
const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});
app.get('/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).send('No such recipe');
return;
}
res.send(recipe.title);
});
app.use('/api/recipes', recipesRouter);
app.use('/api/recipes/:id/reviews', reviewsRouter);
app.get('/about', (req, res) => {
res.send('Recipe Box collects family recipes.');
});
app.get('/menu', (req, res) => {
res.redirect(301, '/recipes');
});
app.get('/menu/:id', (req, res) => {
res.redirect(301, `/recipes/${req.params.id}`);
});
app.use((req, res) => {
if (req.path.startsWith('/api/')) {
res.status(404).json({ error: 'Not found' });
return;
}
res.status(404).send('<h1>Page not found</h1><p><a href="/">Back to Recipe Box</a></p>');
});
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Something went wrong' });
});
app.listen(3000, () => {
console.log('Recipe Box is running on port 3000');
});