Menu

טיפול בשגיאות ב-Express: error middleware ושגיאות async

error handler ב-Express הוא middleware עם ארבעה ארגומנטים, (err, req, res, next), שנרשם אחרי ה-routes. Express 5 שולח אליו שגיאות שנזרקו, קריאות ל-next(err) ו-promises שנדחו מ-handlers אסינכרוניים.

בדף הזה יש עורכים שאפשר להריץ. לערוך, להריץ ולראות את הפלט מיד.

Express מטפל בשגיאות עם middleware מיוחד שמקבל ארבעה ארגומנטים, (err, req, res, next), ונרשם אחרי כל ה-routes שלכם. Express שולח אליו כל שגיאה שנזרקת בתוך route, כל שגיאה שמעבירים ל-next(err), וב-Express 5 כל promise שנדחה מ-handler שהוא async. ה-handler מחליט על קוד הסטטוס ושולח תשובת שגיאה אחת, כך ששאר הקוד יכול פשוט לזרוק.

GET /users/99 עונה 404 עם {"error":"No user with id 99"}, וה-Console מציג את השורה מה-error handler. שלחו GET /users/1 וה-route עונה כרגיל; ה-error handler רץ רק כשמשהו משתבש.

שגיאות ש-Express תופס בשבילכם

יש שלוש דרכים להגיע ל-error handler מתוך route, וב-Express 5 שלושתן עובדות אותו דבר:

GET /async עונה 500 עם {"error":"Rejected in an async route"}; ‏GET /sync ו-GET /next מגיעים לאותו handler עם ההודעות שלהם. עכשיו הסירו את next מהפרמטרים של ה-error handler ושלחו שוב GET /sync: עם שלושה פרמטרים Express מתייחס אליו כ-middleware רגיל, מדלג עליו ועונה עם דף השגיאה ה-HTML של ברירת המחדל שלו.

שגיאות async: ‏Express 5 מול Express 4

ה-route של /async עובד כי Express 5 תופס את ה-promise שנדחה. Express 4 לא תפס: השגיאה הפכה ל-unhandled rejection והבקשה נתקעה.

Express 4Express 5
throw ב-handler סינכרונינתפסנתפס
next(err)נתפסנתפס
promise שנדחה מ-handler שהוא asyncלא נתפס, הבקשה נתקעתנתפס
שגיאה ב-setTimeout או ב-callback של אירועלא נתפס, התהליך קורסלא נתפס, התהליך קורס

ב-Express 4, עטפו כל handler אסינכרוני או התקינו את החבילה express-async-errors:

// Express 4 only
const asyncHandler = (fn) => (req, res, next) =>
    Promise.resolve(fn(req, res, next)).catch(next);

app.get('/users/:id', asyncHandler(async (req, res) => {
    const user = await db.findUser(req.params.id);
    res.json(user);
}));

השורה האחרונה זהה בשתי הגרסאות: Express לא יכול לראות שגיאה שנזרקה ב-callback שהוא לא קרא לו. הפכו APIs של callbacks ל-promises (לרוב Node יש גרסת promise, כמו fs/promises) ועשו להם await.

handler ל-404 ושגיאת JSON אחידה

קל יותר להשתמש ב-API כשלכל שגיאה יש אותו מבנה. תנו לשגיאות status, הפכו routes שלא נמצאה להם התאמה לשגיאת 404, ותנו ל-handler אחד לעצב את כולן:

GET /nope לא תואם לאף route, אז ה-handler של 404 הופך אותו לשגיאה והתשובה היא {"error":"Cannot GET /nope","status":404}. שלחו GET /users/99 והשגיאה שה-route זורק מקבלת את אותו מבנה. שלחו POST /users עם הגוף {"name": }: ‏express.json() לא מצליח לפענח אותו וקורא ל-next() עם שגיאה שה-status שלה הוא 400, ולכן התשובה היא {"error":"Unexpected token '}', \"{\"name\": }\" is not valid JSON","status":400}.

לא לחשוף stack traces

הודעות שגיאה מה-HttpError שלכם נכתבות בשביל הלקוח. הודעות ו-stacks מכל דבר אחר (driver של מסד נתונים, שגיאת הקלדה שזורקת TypeError) יכולים לחשוף נתיבי קבצים, שאילתות וגרסאות של ספריות. ה-handler שלמעלה שולח Internal server error לכל 5xx ורושם את השגיאה האמיתית בשרת במקום. if (res.headersSent) return next(err); מכסה שגיאה שקורית אחרי שחלק מהתשובה כבר נשלח: הצעד הבטוח היחיד אז הוא לתת ל-Express לסגור את החיבור.

ה-handler של ברירת המחדל של Express עצמו תלוי ב-NODE_ENV: הוא כולל את ה-stack trace בדף ה-HTML שלו, אלא אם NODE_ENV הוא production. הגדירו אותו בשרת ה-production שלכם (NODE_ENV=production node server.js), אבל אל תסמכו עליו: handler משלכם שאף פעם לא שולח את err.stack בטוח יותר.

טעויות נפוצות

לרשום את ה-error handler לפני ה-routes. Express מעביר שגיאות רק קדימה, כך ש-handler שנמצא מעל route אף פעם לא רואה את השגיאות שלו. שימו אותו אחרון, אחרי ה-handler של 404.

לתפוס שגיאה ולא לעשות איתה כלום. try/catch שרק רושם ללוג משאיר את הבקשה תלויה. ב-catch, קראו ל-next(err), זרקו שוב, או שלחו תשובה.

לשלוח שתי תשובות. זריקה אחרי res.json() גורמת ל-error handler לנסות לשלוח שוב. בדקו את res.headersSent, כמו בדוגמה.

איך middleware רגיל ו-next() עובדים, ראו middleware ב-Express.

שאלות נפוצות

אפשר שיהיה יותר מ-error handler אחד ב-Express?

כן. הם רצים לפי הסדר, וכל אחד או שולח תשובה או קורא ל-next(err) כדי להעביר את השגיאה הלאה. חלוקה נפוצה היא handler אחד שרושם ללוג ואחד ששולח את התשובה.

מה קורה אם error handler זורק?

Express מעביר את השגיאה החדשה ל-error handler הבא, ואם אין כזה, ל-handler המובנה שלו, ששולח דף 500. שמרו על error handlers פשוטים כדי שהם עצמם לא יוכלו להיכשל.

באיזה קוד סטטוס תשובת שגיאה צריכה להשתמש?

השתמשו ב-4xx כשהלקוח שלח משהו שגוי (400 קלט שגוי, 401 לא מחובר, 403 אין הרשאה, 404 לא נמצא, 409 התנגשות) וב-500 כשהקוד שלכם נכשל. לעולם אל תענו 200 עם שגיאה בגוף.

איך מחזירים שגיאות ולידציה עבור כמה שדות?

ענו 400 עם רשימה, למשל { "errors": [{ "field": "email", "message": "is required" }] }. עם zod, מפו את result.error.issues למבנה הזה ב-error handler, כך שכל route מדווח על קלט שגוי באותה צורה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל