Express obsługuje błędy specjalnym middleware, który przyjmuje cztery argumenty, (err, req, res, next), i jest rejestrowany po wszystkich trasach. Express przekazuje mu każdy błąd rzucony w trasie, każdy błąd przekazany do next(err), a w Express 5 każdy odrzucony promise z handlera async. Handler ustala kod statusu i wysyła jedną odpowiedź z błędem, więc reszta twojego kodu może po prostu rzucać wyjątki.
GET /users/99 odpowiada 404 z {"error":"No user with id 99"}, a Console pokazuje linię z error handlera. Wyślij GET /users/1, a trasa odpowie normalnie; error handler działa tylko wtedy, gdy coś pójdzie źle.
Błędy, które Express łapie za ciebie
Są trzy sposoby, żeby z trasy trafić do error handlera, i w Express 5 wszystkie działają tak samo:
GET /async odpowiada 500 z {"error":"Rejected in an async route"}; GET /sync i GET /next kończą w tym samym handlerze ze swoimi komunikatami. Teraz usuń next z parametrów error handlera i wyślij jeszcze raz GET /sync: z trzema parametrami Express traktuje go jak zwykły middleware, pomija go i odpowiada swoją domyślną stroną błędu w HTML.
Błędy async: Express 5 vs Express 4
Trasa /async działa, bo Express 5 łapie odrzucony promise. Express 4 tego nie robił: błąd stawał się nieobsłużonym odrzuceniem, a żądanie wisiało.
| Express 4 | Express 5 | |
|---|---|---|
throw w handlerze synchronicznym | Złapany | Złapany |
next(err) | Złapany | Złapany |
Odrzucony promise z handlera async | Niezłapany, żądanie wisi | Złapany |
Błąd w callbacku setTimeout lub zdarzenia | Niezłapany, proces się wywraca | Niezłapany, proces się wywraca |
W Express 4 opakuj każdy handler async albo zainstaluj pakiet 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);
}));
Ostatni wiersz jest taki sam w obu wersjach: Express nie widzi błędu rzuconego w callbacku, którego sam nie wywołał. Zamieniaj API oparte na callbackach na promise (większość Node ma wersję z promise, np. fs/promises) i czekaj na nie przez await.
Handler 404 i spójny błąd w JSON
API jest wygodniejsze, gdy każdy błąd ma ten sam kształt. Daj błędom status, zamień niedopasowane trasy na błąd 404 i pozwól jednemu handlerowi sformatować wszystkie:
GET /nope nie pasuje do żadnej trasy, więc handler 404 zamienia je w błąd, a odpowiedź to {"error":"Cannot GET /nope","status":404}. Wyślij GET /users/99, a błąd rzucony przez trasę dostanie ten sam kształt. Wyślij POST /users z body {"name": }: express.json() nie potrafi go sparsować i wywołuje next() z błędem, którego status to 400, więc odpowiedź to {"error":"Unexpected token '}', \"{\"name\": }\" is not valid JSON","status":400}.
Nie ujawniaj stack trace
Komunikaty z twojego własnego HttpError są pisane dla klienta. Komunikaty i stacki z całej reszty (sterownik bazy danych, literówka, która rzuca TypeError) mogą zdradzić ścieżki plików, zapytania i wersje bibliotek. Handler powyżej przy każdym 5xx wysyła Internal server error, a prawdziwy błąd loguje na serwerze. if (res.headersSent) return next(err); obsługuje błąd, który zdarzył się, gdy część odpowiedzi była już wysłana: jedyne bezpieczne wyjście to pozwolić Express zamknąć połączenie.
Domyślny handler Express kieruje się NODE_ENV: dołącza stack trace do swojej strony HTML, chyba że NODE_ENV to production. Ustaw tę zmienną na serwerze produkcyjnym (NODE_ENV=production node server.js), ale nie polegaj na niej: własny handler, który nigdy nie wysyła err.stack, jest bezpieczniejszy.
Częste błędy
Rejestrowanie error handlera przed trasami. Express przekazuje błędy tylko do przodu, więc handler nad trasą nigdy nie zobaczy jej błędów. Umieść go na końcu, po handlerze 404.
Złapanie błędu i nic z nim nie robienie. try/catch, który tylko loguje, zostawia wiszące żądanie. W catch wywołaj next(err), rzuć ponownie albo wyślij odpowiedź.
Wysyłanie dwóch odpowiedzi. Rzucenie wyjątku po res.json() sprawia, że error handler próbuje wysłać odpowiedź jeszcze raz. Sprawdzaj res.headersSent, jak w przykładzie.
Jak działa zwykły middleware i next(), przeczytasz w Express middleware.
Najczęściej zadawane pytania
Czy w Express może być więcej niż jeden error handler?
Tak. Wykonują się po kolei, a każdy albo wysyła odpowiedź, albo wywołuje next(err), żeby przekazać błąd dalej. Częsty podział to jeden handler, który loguje, i drugi, który wysyła odpowiedź.
Co się stanie, gdy error handler sam rzuci wyjątek?
Express przekaże nowy błąd do następnego error handlera, a jeśli go nie ma, do wbudowanego, który wysyła stronę 500. Pisz proste error handlery, żeby same nie mogły zawieść.
Jakiego kodu statusu użyć w odpowiedzi z błędem?
4xx, gdy klient wysłał coś źle (400 złe dane, 401 niezalogowany, 403 brak uprawnień, 404 nie znaleziono, 409 konflikt), i 500, gdy zawiódł twój kod. Nigdy nie odpowiadaj 200 z błędem w body.
Jak zwrócić błędy walidacji dla kilku pól?
Odpowiedz 400 z listą, np. { "errors": [{ "field": "email", "message": "is required" }] }. Z zod zamień w error handlerze result.error.issues na taki kształt, żeby każda trasa zgłaszała złe dane tak samo.