Fehlerbehandlung in Web-APIs
Gute Fehlerbehandlung macht deine API robust und nachvollziehbar. Du lernst zentrale Error-Handler, sinnvolle Statuscodes und einheitliche Fehlerformate in Node.js und Express.
Fehler passieren immer: Eingaben sind ungültig, die Datenbank ist nicht erreichbar oder ein Datensatz fehlt. Eine gute API geht damit kontrolliert um, statt einfach abzustürzen oder kryptische Meldungen zu liefern. In diesem Beitrag zeige ich dir, wie du Fehlerbehandlung in Web-APIs mit Node.js und Express sauber umsetzt.
Warum Fehlerbehandlung so wichtig ist
Wenn ein Fehler unbehandelt bleibt, bekommt der Client im besten Fall einen nichtssagenden 500er, im schlimmsten Fall einen kompletten Crash deines Servers. Beides ist schlecht. Gute Fehlerbehandlung sorgt dafür, dass:
- der Client eine klare, verständliche Antwort erhält,
- der passende HTTP-Statuscode gesetzt wird,
- du intern genug Informationen zum Debuggen behältst.
Das macht deine API robust und für andere Entwickler nachvollziehbar.
Fehler in synchronem Code
Bei synchronem Code nutzt du try/catch, um Fehler abzufangen und gezielt zu reagieren:
app.get("/parse", (req, res) => {
try {
const daten = JSON.parse(req.query.payload);
res.json({ data: daten });
} catch (err) {
res.status(400).json({ error: "Ungueltiges JSON" });
}
});Hier fängst du den Parse-Fehler ab und antwortest mit 400, statt den Server mit einer unbehandelten Exception zu belasten.
Fehler in asynchronem Code
Bei async-Funktionen musst du Fehler an den Express-Error-Handler weiterreichen. Das geschieht über next(err):
app.get("/users/:id", async (req, res, next) => {
try {
const user = await db.users.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: "Nicht gefunden" });
}
res.json({ data: user });
} catch (err) {
next(err); // an den zentralen Handler weiterreichen
}
});Vergisst du das try/catch, bleibt der Fehler hängen und die Anfrage läuft ins Leere. Deshalb ist dieses Muster so wichtig.
Ein zentraler Error-Handler
Express kennt spezielle Middleware mit vier Parametern, die als zentraler Fehler-Handler dient. Du definierst sie am Ende deiner Routen:
// Diese Middleware faengt alle weitergereichten Fehler
app.use((err, req, res, next) => {
console.error(err.stack); // intern protokollieren
const status = err.status || 500;
res.status(status).json({
error: err.message || "Interner Serverfehler"
});
});So hast du eine einzige Stelle, an der du alle Fehler einheitlich behandelst und protokollierst. Das spart dir viel doppelten Code in den einzelnen Routen.
Eigene Fehlerklassen
Für aussagekräftige Fehler lohnt sich eine eigene Fehlerklasse, die den Statuscode gleich mitführt:
class ApiError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
app.get("/produkte/:id", async (req, res, next) => {
try {
const produkt = await db.produkte.findById(req.params.id);
if (!produkt) {
throw new ApiError(404, "Produkt nicht gefunden");
}
res.json({ data: produkt });
} catch (err) {
next(err);
}
});Der zentrale Handler liest dann einfach err.status und err.message aus. So bleibt deine Fehlerlogik konsistent und gut lesbar.
Was du dem Client zeigst
Achte darauf, keine sensiblen Details nach außen zu geben. Stack-Traces oder Datenbankfehler gehören ins interne Log, nicht in die Antwort an den Nutzer:
- Logge die vollständige Fehlermeldung serverseitig.
- Sende dem Client nur eine verständliche, unkritische Nachricht.
- Nutze einheitliche Felder wie
errorodercode, damit Clients zuverlässig reagieren können.
Fazit
Saubere Fehlerbehandlung ist das Rückgrat einer robusten API. Fange synchrone Fehler mit try/catch ab, reiche asynchrone Fehler mit next(err) weiter und bündle alles in einem zentralen Error-Handler. Mit eigenen Fehlerklassen bleibt dein Code übersichtlich. Trenne klar zwischen internem Log und der Antwort an den Client, dann ist deine API sicher und angenehm zu nutzen.