وصفة اليوم
جزء من قسم أساسيات Express في رحلة Node.js على Coddy. الدرس 19 من 20.
يحصل صندوق الوصفات على وصفة اليوم: الوصفة نفسها للجميع طوال اليوم، ووصفة مختلفة غدًا. يحددها التاريخ، ويتيح لك ?date= تجربة أي يوم.
يمثل new Date('2026-03-01') منتصف الليل بتوقيت UTC في ذلك اليوم. يُحتسب يوم السنة ابتداءً من 31 ديسمبر، لذا فإن 1 يناير هو اليوم 1: (date - Date.UTC(year, 0, 0)) / 86400000، لأن اليوم يحتوي على 86,400,000 ميلي ثانية. النص الذي لا يمثل تاريخًا يعطي تاريخًا غير صالح، وتكون قيمة getTime() له NaN.
أضف المسار في server.js، أعلى معالج 404. اختر من الوصفات الموجودة في data/recipes.json، كما تفعل واجهة API، لكي تصبح الوصفة التي ينشرها أحدهم وصفة اليوم: صدّر loadRecipes من routes/recipes.js وانتظر نتيجته باستخدام await.
التحدي
سهل- يُرجع
GET /api/today?date=2026-03-01القيمةrecipes[day % recipes.length]بتنسيق JSON، حيث تأتيrecipesمنloadRecipes()وتكونdayرقم اليوم في السنة. - من دون
date: تاريخ اليوم. - تاريخ يتعذر تحليله:
400مع{ "error": "Use a date like 2026-03-01" }.
أرسل ?date=2026-03-01 مرتين، ثم ?date=soon.
جرّب بنفسك
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') نصًا عاديًا، لذا يظهر كل عنوان في سطر مستقل.
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');
});