מטפל 404
חלק מהיחידה יסודות Express במסלול ה-Node.js של Coddy. שיעור 14 מתוך 20.
Express משיב לנתיב לא מוכר באמצעות עמוד משלו, Cannot GET /x, שאינו מתאים לאתר שלך וגם לא ל־API שלך.
app.use((req, res) => {
res.status(404).send('No forecast here. Try /weather/oslo');
});תווכה ללא נתיב פועלת עבור כל בקשה שמגיעה אליה. מקמים אותה אחרי כל הנתיבים ואחרי express.static: בקשות שהם השיבו להן לעולם לא מגיעות אליה, ולכן היא רואה רק את הבקשות שאיש לא השיב להן. היא משיבה לבקשות, ולכן אין צורך ב־next.
req.path הוא הנתיב ללא מחרוזת השאילתה, ולכן req.path.startsWith('/api/') מאפשר לזהות קריאה ל־API ולהבדיל בינה לבין עמוד.
אתגר
קל- נתיב תחת
/api/שאין לו מענה מחזיר404עם{ "error": "Not found" }. - כל נתיב אחר מחזיר דף HTML של
404שכתוב בוPage not found.
שלחו GET /api/nope, פתחו את /nope בתצוגת העמוד, ואז שלחו GET /api/recipes/1.
נסו בעצמכם
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.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.get('/api/recipes', (req, res) => {
res.json(recipes);
});
app.get('/api/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).json({ error: 'No such recipe' });
return;
}
res.json(recipe);
});
app.post('/api/recipes', (req, res) => {
const { title, tags } = req.body;
if (!title) {
res.status(400).json({ error: 'A recipe needs a title' });
return;
}
const recipe = { id: recipes.length + 1, title, tags: tags ?? [] };
recipes.push(recipe);
res.status(201).json(recipe);
});
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.listen(3000, () => {
console.log('Recipe Box is running on port 3000');
});