Menu
CoddyTech

מטפל 404

חלק מהיחידה יסודות Express במסלול ה-Node.js של Coddy. שיעור 16 מתוך 23.

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

אתגר

קל

נתיבים שאינם קיימים מציגים את דף ברירת המחדל של Express, Cannot GET. החליפו אותו בדף משלכם: JSON עבור ה-API, ודף ידידותי למבקרים.

  1. ב-server.js, אחרי כל נתיב ואחרי express.static, הוסיפו app.use אחרון ללא נתיב. בקשות שכבר קיבלו מענה לא מגיעות אליו, ולכן הוא רואה רק בקשות ששום דבר אחר לא טיפל בהן.
  2. כשהנתיב מתחיל ב-/api/, הוא מחזיר סטטוס 404 ואת ה-JSON הזה: {"error": "Not found"}
  3. עבור כל נתיב אחר, הוא מחזיר סטטוס 404 ודף HTML שמכיל את המילים Page not found, למשל <h1>Page not found</h1>. קישור חזרה לדף הבית הוא תוספת נחמדה.

נסו זאת: לחצו על Run ושלחו את /api/nope. אתם אמורים לראות סטטוס 404 ואת {"error":"Not found"}. שלחו את /nope: סטטוס 404, שמוצג כדף שאומר Page not found. שלחו את /api/recipes/1: סטטוס 200 ואת Tomato Soup, מה שמראה שהנתיבים שלכם עדיין מקבלים קדימות.

נסו בעצמכם

import { escapeHtml } from './html.js';
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'));

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

תרגלו בעצמכם: Express אונליין