Công thức hôm nay
Thuộc phần Kiến thức cơ bản về Express trong hành trình Node.js của Coddy. Bài học 22 trên 23.
Recipe Box có thể dùng các kỹ năng quen thuộc về định tuyến và đọc tệp để cung cấp một công thức cho mỗi ngày. Hàm hỗ trợ dayOfYear được cung cấp sẽ chuyển ngày thành một con số: ngày 1 tháng 1 là 1 và ngày 1 tháng 3 năm 2026 là 60. Nếu thiếu ngày, hệ thống sẽ dùng ngày hôm nay; văn bản không đọc được sẽ trả về null.
const day = dayOfYear(req.query.date);
// ?date=2026-03-01 cho ra 60
// ?date=soon cho ra nullDùng toán tử lấy phần dư để chọn một vị trí: day % recipes.length. Với bốn công thức, ngày 60 chọn vị trí không. Hãy tải các công thức bên trong yêu cầu để cả những công thức mới đăng cũng được tính đến. Phần phân tích ngày đã có sẵn; hãy tập trung vào việc xây dựng tuyến.
Thử thách
DễThêm endpoint Món ăn hôm nay vào Recipe Box. Tệp day-of-year.js được cung cấp xuất dayOfYear(text), hàm này trả về số thứ tự ngày hoặc null nếu ngày không hợp lệ.
- Xuất
loadRecipestừroutes/recipes.js. Nhập hàm này vàoserver.js: router vẫn là import mặc định, còn{ loadRecipes }theo sau router, cách nhau bằng dấu phẩy. - Nhập
dayOfYear. Trongserver.js, thêm một routeasyncchoGET /api/todayphía trên trình xử lý 404. Truyềnreq.query.datecho hàm hỗ trợ. - Nếu kết quả là
null, trả về mã 400 cùng với{"error":"Use a date like 2026-03-01"}. - Nếu không, hãy chờ
loadRecipes()hoàn tất rồi trả vềrecipes[day % recipes.length]dưới dạng JSON. Giữ nguyên các route hiện có và trình xử lý lỗi.
Thử GET /api/today?date=2026-03-01: endpoint trả về món ăn đầu tiên vì số thứ tự ngày là 60 và có bốn món ăn. Thử ?date=soon: endpoint trả về lỗi 400 ở trên.
Tự làm thử
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') gửi văn bản thuần túy, nên mỗi tiêu đề hiển thị trên một dòng riêng.
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');
});
Tất cả bài học trong Kiến thức cơ bản về Express
Tự luyện tập: Playground Express