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 4 | Express 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 מדווח על קלט שגוי באותה צורה.