Menu
CoddyTech

معالج 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 أم لصفحة.

challenge icon

التحدي

سهل
  • المسار ضمن /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('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;');

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');
});

جميع دروس أساسيات Express