Menu

Express Error Handling: Error-Middleware und async-Fehler

Ein Error Handler in Express ist eine Middleware mit vier Argumenten, (err, req, res, next), die nach deinen Routen registriert wird. Express 5 schickt ihr geworfene Fehler, next(err)-Aufrufe und abgelehnte Promises aus async-Handlern.

Diese Seite enthält ausführbare Editoren. Bearbeiten, ausführen und Ausgabe sofort sehen.

Express behandelt Fehler mit einer besonderen Middleware, die vier Argumente nimmt, (err, req, res, next), und nach allen Routen registriert wird. Express schickt ihr jeden Fehler, der in einer Route geworfen wird, jeden Fehler, den du an next(err) übergibst, und in Express 5 jedes abgelehnte Promise aus einem async-Handler. Der Handler legt den Statuscode fest und sendet eine Fehlerantwort, der Rest deines Codes kann also einfach werfen.

GET /users/99 antwortet mit 404 und {"error":"No user with id 99"}, und die Console zeigt die Zeile aus dem Error Handler. Sende GET /users/1, und die Route antwortet normal; der Error Handler läuft nur, wenn etwas schiefgeht.

Fehler, die Express für dich fängt

Es gibt drei Wege, aus einer Route zum Error Handler zu kommen, und in Express 5 funktionieren alle drei gleich:

GET /async antwortet mit 500 und {"error":"Rejected in an async route"}; GET /sync und GET /next landen mit ihren eigenen Meldungen im selben Handler. Entferne jetzt next aus den Parametern des Error Handlers und sende noch einmal GET /sync: Mit drei Parametern behandelt Express ihn als normale Middleware, überspringt ihn und antwortet mit seiner Standard-HTML-Fehlerseite.

async-Fehler: Express 5 vs. Express 4

Die Route /async funktioniert, weil Express 5 das abgelehnte Promise fängt. Express 4 tat das nicht: Der Fehler wurde zu einer unbehandelten Ablehnung, und die Anfrage hing.

Express 4Express 5
throw in einem synchronen HandlerGefangenGefangen
next(err)GefangenGefangen
Abgelehntes Promise aus einem async-HandlerNicht gefangen, Anfrage hängtGefangen
Fehler in einem Callback von setTimeout oder einem EventNicht gefangen, Prozess stürzt abNicht gefangen, Prozess stürzt ab

Unter Express 4 umhüllst du jeden async-Handler oder installierst das Paket 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);
}));

Die letzte Zeile ist in beiden Versionen gleich: Express sieht keinen Fehler, der in einem Callback geworfen wird, den es nicht selbst aufgerufen hat. Mach aus Callback-APIs Promises (fast alles in Node hat eine Promise-Variante, etwa fs/promises) und warte mit await auf sie.

Ein 404-Handler und ein einheitlicher JSON-Fehler

Eine API ist leichter zu nutzen, wenn jeder Fehler dieselbe Form hat. Gib Fehlern einen status, mach aus Routen ohne Treffer einen 404-Fehler und lass einen Handler alle formatieren:

GET /nope passt auf keine Route, also macht der 404-Handler daraus einen Fehler, und die Antwort ist {"error":"Cannot GET /nope","status":404}. Sende GET /users/99, und der Fehler, den die Route wirft, bekommt dieselbe Form. Sende POST /users mit dem Body {"name": }: express.json() kann ihn nicht parsen und ruft next() mit einem Fehler auf, dessen status 400 ist, deshalb lautet die Antwort {"error":"Unexpected token '}', \"{\"name\": }\" is not valid JSON","status":400}.

Keine Stack Traces nach außen geben

Fehlermeldungen aus deinem eigenen HttpError sind für den Client geschrieben. Meldungen und Stacks von allem anderen (ein Datenbanktreiber, ein Tippfehler, der TypeError wirft) können Dateipfade, Queries und Bibliotheksversionen verraten. Der Handler oben sendet für jeden 5xx Internal server error und loggt den echten Fehler stattdessen auf dem Server. if (res.headersSent) return next(err); deckt einen Fehler ab, der passiert, nachdem ein Teil der Antwort schon gesendet wurde: Dann ist das einzig Sichere, Express die Verbindung schließen zu lassen.

Der eingebaute Standard-Handler von Express richtet sich nach NODE_ENV: Er nimmt den Stack Trace in seine HTML-Seite auf, außer NODE_ENV ist production. Setz es auf deinem Produktionsserver (NODE_ENV=production node server.js), aber verlass dich nicht darauf: Ein eigener Handler, der nie err.stack sendet, ist sicherer.

Häufige Fehler

Den Error Handler vor den Routen registrieren. Express gibt Fehler nur nach vorne weiter, ein Handler über einer Route sieht deren Fehler also nie. Setz ihn ans Ende, nach dem 404-Handler.

Einen Fehler fangen und nichts damit tun. Ein try/catch, das nur loggt, lässt die Anfrage hängen. Ruf im catch next(err) auf, wirf erneut oder sende eine Antwort.

Zwei Antworten senden. Wenn du nach res.json() wirfst, versucht der Error Handler, noch einmal zu senden. Prüf res.headersSent wie im Beispiel.

Wie normale Middleware und next() funktionieren, steht unter Express Middleware.

Häufig gestellte Fragen

Kann ich in Express mehr als einen Error Handler haben?

Ja. Sie laufen der Reihe nach, und jeder sendet entweder eine Antwort oder ruft next(err) auf, um den Fehler weiterzugeben. Eine übliche Aufteilung ist ein Handler, der loggt, und einer, der die Antwort sendet.

Was passiert, wenn ein Error Handler selbst einen Fehler wirft?

Express gibt den neuen Fehler an den nächsten Error Handler weiter und, wenn es keinen gibt, an seinen eingebauten, der eine 500-Seite sendet. Halte Error Handler einfach, damit sie nicht selbst fehlschlagen können.

Welchen Statuscode sollte eine Fehlerantwort haben?

Nimm 4xx, wenn der Client etwas Falsches geschickt hat (400 ungültige Eingabe, 401 nicht eingeloggt, 403 nicht erlaubt, 404 nicht gefunden, 409 Konflikt), und 500, wenn dein Code fehlgeschlagen ist. Antworte nie mit 200 und einem Fehler im Body.

Wie gebe ich Validierungsfehler für mehrere Felder zurück?

Antworte mit 400 und einer Liste, zum Beispiel { "errors": [{ "field": "email", "message": "is required" }] }. Mit zod bildest du result.error.issues im Error Handler auf diese Form ab, damit jede Route ungültige Eingaben gleich meldet.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

Los geht's