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

שרת שקורס מכל שגיאה, או מחזיר ללקוח את ה-stack trace המלא, הוא לא שרת מוכן. בשיעור הזה נבנה טיפול בשגיאות מסודר: מקום אחד שתופס הכול, שגיאות עם קוד סטטוס נכון, ותשובה אחידה ללקוח.

Middleware של שגיאות

Middleware עם ארבעה פרמטרים הוא מטפל שגיאות. Express מזהה אותו לפי מספר הפרמטרים, ולכן חייבים לכתוב את כולם, גם אם לא משתמשים ב-next:

// אחרון, אחרי כל ה-Routes
app.use((error, req, res, next) => {
    console.error(error);
    res.status(500).json({ error: 'משהו השתבש בשרת' });
});

איך שגיאה מגיעה אליו

// 1. זריקה בקוד סינכרוני - נתפסת אוטומטית
app.get('/sync', (req, res) => {
    throw new Error('נכשל');
});

// 2. שגיאה בפונקציה async - נתפסת אוטומטית ב-Express 5
app.get('/async', async (req, res) => {
    const user = await db.findUser(req.params.id);  // אם זה נכשל, מגיע למטפל
    res.json(user);
});

// 3. העברה ידנית עם next(error)
app.get('/manual', (req, res, next) => {
    next(new Error('נכשל'));
});
Express 4 מול 5: ב-Express 4, שגיאה בתוך async לא נתפסה, והבקשה נתקעה. לכן בקוד ישן תראו try/catch עם next(error) בכל Route, או חבילה בשם express-async-errors. ב-Express 5 זה כבר לא נחוץ.

שגיאות עם קוד סטטוס

לא כל שגיאה היא 500. "משתמש לא נמצא" היא 404, "קלט לא תקין" היא 400. מחלקת שגיאה משלכם נושאת את הקוד:

// errors.js
export class AppError extends Error {
    constructor(message, status = 500) {
        super(message);
        this.status = status;
    }
}

export class NotFoundError extends AppError {
    constructor(what = 'המשאב') {
        super(`${what} לא נמצא`, 404);
    }
}
import { AppError, NotFoundError } from './errors.js';

app.get('/api/users/:id', async (req, res) => {
    const user = await db.findUser(req.params.id);
    if (!user) throw new NotFoundError('המשתמש');
    res.json(user);
});

מטפל מרכזי אחד

const isProduction = process.env.NODE_ENV === 'production';

// 404: אף Route לא ענה
app.use((req, res) => {
    res.status(404).json({ error: `הנתיב ${req.path} לא קיים` });
});

// כל השגיאות
app.use((error, req, res, next) => {
    const status = error.status ?? 500;

    // שגיאות לא צפויות נרשמות במלואן, בשרת בלבד
    if (status >= 500) console.error(error);

    res.status(status).json({
        error: status >= 500 && isProduction ? 'שגיאה בשרת' : error.message,
    });
});
  • ה-404 הוא Middleware רגיל שנרשם אחרי כל ה-Routes: הוא רץ רק אם אף אחד לא ענה.
  • בפרודקשן לא חושפים את הודעת השגיאה הפנימית (היא יכולה לכלול נתיבים, שאילתות וסודות).
  • JSON לא תקין שנשלח ל-express.json() מגיע למטפל עם status 400 בעצמו.

שגיאות שנפלו מחוץ ל-Express

process.on('unhandledRejection', (reason) => {
    console.error('Promise שנדחה בלי טיפול:', reason);
});

process.on('uncaughtException', (error) => {
    console.error('שגיאה שלא נתפסה:', error);
    process.exit(1);  // המצב לא ידוע: עדיף לעלות מחדש
});

אחרי uncaughtException לא ממשיכים לרוץ כאילו כלום: יוצאים, ומנהל התהליכים (או שירות האחסון) מעלה את השרת מחדש.

דוגמה מלאה להרצה

errors.js כולל מחלקת AppError, נתיבים שמחזירים 400, 401, 404 ו-500, ומטפל מרכזי. הריצו ונסו /not-found ו-/server-error.

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. איך Express מזהה Middleware של שגיאות?

    1. לפי import
    2. לפי המיקום בקובץ
    3. לפי השם
    4. לפי ארבעה פרמטרים: (error, req, res, next)
    הצגת התשובה

    תשובה ד. לכן חייבים לכתוב את כל ארבעת הפרמטרים.

  2. איפה רושמים את מטפל ה-404?

    1. אחרי כל ה-Routes, כדי שירוץ רק אם אף אחד לא ענה
    2. בהתחלה
    3. בקובץ נפרד בלבד
    4. לא צריך
    הצגת התשובה

    תשובה א. Middleware רצים לפי הסדר.

  3. למה לא מחזירים ללקוח את הודעת השגיאה הפנימית בפרודקשן?

    1. הלקוח לא יבין
    2. זה איטי
    3. היא יכולה לחשוף נתיבים, שאילתות וסודות
    4. היא ארוכה
    הצגת התשובה

    תשובה ג. את הפרטים רושמים בלוג של השרת, והלקוח מקבל הודעה כללית.