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 22 de 23.

Recipe Box puede usar las conocidas habilidades para gestionar rutas y leer archivos para ofrecer una receta para cada día. La función auxiliar dayOfYear proporcionada convierte una fecha en un número: el 1 de enero es 1 y el 1 de marzo de 2026 es 60. Si falta la fecha, se usa la de hoy; el texto ilegible devuelve null.

const day = dayOfYear(req.query.date);
// ?date=2026-03-01 da 60
// ?date=soon da null

Usa el operador de resto para elegir una posición: day % recipes.length. Con cuatro recetas, el día 60 selecciona la posición cero. Carga las recetas dentro de la solicitud para que también se incluyan las publicadas recientemente. El análisis de fechas ya está proporcionado; concéntrate en componer la ruta.

challenge icon

Desafío

Fácil

Añade un endpoint de receta del día a Recipe Box. El archivo proporcionado day-of-year.js exporta dayOfYear(text), que devuelve el número del día o null para una fecha no válida.

  1. Exporta loadRecipes desde routes/recipes.js. Impórtalo en server.js: el router sigue siendo la importación predeterminada y { loadRecipes } va después de una coma.
  2. Importa dayOfYear. En server.js, encima del controlador 404, añade una ruta async para GET /api/today. Pasa req.query.date a la función auxiliar.
  3. Para null, devuelve 400 con {"error":"Use a date like 2026-03-01"}.
  4. De lo contrario, espera a que se resuelva loadRecipes() y devuelve recipes[day % recipes.length] como JSON. Conserva las rutas existentes y el controlador de errores.

Prueba GET /api/today?date=2026-03-01: devuelve la primera receta porque el número del día es 60 y hay cuatro recetas. Prueba ?date=soon: devuelve el error 400 anterior.

Pruébalo tú mismo

import { escapeHtml } from './html.js';
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'));

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

Practica por tu cuenta: Playground de Express