Menu
CoddyTech

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.

challenge icon

Desafío

Fácil
  • GET /api/today?date=2026-03-01 responde con recipes[day % recipes.length] en formato JSON; recipes proviene de loadRecipes() y day representa el día del año.
  • Sin date: hoy.
  • Una fecha que no se puede analizar: 400 con { "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('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;');

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');
});

Todas las lecciones de Conceptos básicos de Express