معالج 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');
});