Menu
CoddyTech

טיפול בשגיאות

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

כשנתיב גורם לשגיאה, Express משיב עם דף השגיאה שלו, שאינו JSON ומציג פרטים שמיועדים לך.

app.get('/api/fines/:member', (req, res) => {
    throw new Error('Fines database is down');
});

app.use((err, req, res, next) => {
    console.error(err);
    res.status(500).json({ error: 'Try again later' });
});

תוכנת ביניים עם ארבעה ארגומנטים, (err, req, res, next), היא מטפל בשגיאות. Express סופרת את הארגומנטים: עם שלושה, מדובר בתוכנת ביניים רגילה, והשגיאות מדלגות עליה. כשנתיב גורם לשגיאה, Express מדלגת על תוכנת הביניים הרגילה וקוראת למטפל השגיאות עם השגיאה. הוסף אותו בסוף, אחרי מטפל 404. רשום את הפרטים ביומן לשימושך; שלח ללקוח הודעה קצרה.

throw new Error('text') עוצר את הנתיב ומעביר את השגיאה ל-Express, וגם הקריאה ל-next(err) עושה זאת. השב עם 400 כשהמבקר שלח משהו שגוי ויכול לתקן אותו. זרוק שגיאה כשהשרת אשם.

challenge icon

אתגר

קל

ממשיכים עם Recipe Box. אפשר להתעלם מקובצי practice-errors/ הנפרדים; לחיצה על הפעלה מפעילה עכשיו את server.js של הפרויקט.

טבחים רוצים להתאים את הכמויות במתכון, למשל להכין פי שניים מנות למסיבה. אי אפשר להתאים את הכמויות במתכון שנשמר בלי מספר מנות. זו תקלה בנתונים שלך, ולא משהו שהמבקר יכול לתקן, ולכן היא מועברת למטפל שגיאות שמחזיר תשובת JSON נקייה עם סטטוס 500.

  1. ב-server.js, מעל מטפל השגיאות של 404, הוסיפו נתיב עבור /api/recipes/:id/scale. הנתיב קורא את המקדם ממחרוזת השאילתה, כמו ב-/api/recipes/1/scale?by=2.
  2. אם אין מתכון עם המזהה הזה, החזירו תשובה עם סטטוס 404 ו-{"error": "No such recipe"}.
  3. המירו את by למספר. אם הוא אינו מספר, או שהוא 0 או פחות, החזירו תשובה עם סטטוס 400 ו-{"error": "Scale by a positive number"}. Number('lots') הוא NaN, ו-Number.isNaN מזהה אותו.
  4. אם אין למתכון servings, זרקו Error חדש שההודעה שלו היא Recipe 5 has no servings, כאשר 5 מוחלף במזהה של אותו מתכון.
  5. אחרת, החזירו JSON שמכיל רק את הכותרת ואת מספר המנות לאחר התאמת הכמויות. במתכון 1, Tomato Soup, יש 4 מנות, ולכן /api/recipes/1/scale?by=2 מחזיר {"title": "Tomato Soup", "servings": 8}. אל תוסיפו שדות אחרים.
  6. בסוף רשימת הביניים של הקובץ, אחרי מטפל השגיאות של 404, הוסיפו מטפל שגיאות: קריאה ל-app.use שהפונקציה שלה מקבלת ארבעה ארגומנטים, (err, req, res, next). היא מדפיסה את השגיאה באמצעות console.error ומחזירה תשובה עם סטטוס 500 ו-{"error": "Something went wrong"}.

נסו זאת: לחצו על הפעלה ושלחו /api/recipes/1/scale?by=2. אתם אמורים לראות {"title":"Tomato Soup","servings":8}. שלחו /api/recipes/2/scale?by=lots: סטטוס 400 ו-{"error":"Scale by a positive number"}. שלחו /api/recipes/99/scale?by=2: סטטוס 404. עכשיו בחרו POST ושלחו {"title": "Toast"} אל /api/recipes. הוא הופך למתכון 5, ללא מספר מנות. שלחו /api/recipes/5/scale?by=2: סטטוס 500 ו-{"error":"Something went wrong"}, ובמסוף תופיע השגיאה Recipe 5 has no servings.

נסו בעצמכם

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.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.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה יסודות Express

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