ein Katalog für strukturierte, maschinenlesbare Fehlerantworten
Ein message-Text allein reicht keinem Client, um eine Fehlermeldung zu übersetzen oder eine Retry-Logik zu steuern. Wer GraphQL Error Codes standardisieren will, braucht einen zentralen Katalog konsistenter extensions.code-Werte, der Domain-Exceptions eindeutig auf stabile, dokumentierte Fehlercodes abbildet.
Inhaltsverzeichnis
- 1. Warum ein message-Text allein nicht ausreicht
- 2. extensions.code: der maschinenlesbare Kern jedes GraphQL-Fehlers
- 3. Einen zentralen Error-Code-Katalog aufbauen
- 4. Domain-Exceptions auf Error Codes abbilden
- 5. Fehlerkategorien: Validation, Auth, Business, System
- 6. Error Formatting: von der Exception zur GraphQL-Response
- 7. Internationalisierung: Codes stabil, Messages übersetzt
- 8. Clientseitige Fehlerbehandlung anhand von Error Codes
- 9. Fehleransätze im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum ein message-Text allein nicht ausreicht
In vielen GraphQL-APIs sieht eine Fehlerantwort aus wie ein Freitext: "message": "Product not found", ohne weitere Struktur. Für einen Menschen, der die API während der Entwicklung testet, ist das ausreichend, für einen Client in Produktion ist es unbrauchbar. Wer GraphQL Error Codes standardisieren will, erkennt schnell, dass Clients nicht auf Textinhalte prüfen dürfen, weder für Übersetzungen noch für Verzweigungslogik, weil sich Formulierungen ändern, ohne dass sich die Fehlerursache ändert.
Das GraphQL-Spec erlaubt genau für diesen Zweck das Feld extensions in jedem Fehlerobjekt, einen frei definierbaren Bereich, in dem strukturierte Zusatzinformationen mitgegeben werden können. Die Praxis hat sich auf extensions.code als De-facto-Standard geeinigt, einen stabilen, maschinenlesbaren String, der unabhängig von der menschenlesbaren Message bleibt. Ohne diesen strukturierten Ansatz bleibt jede Fehlerbehandlung im Client fragil und an die aktuelle Textformulierung im Backend gekoppelt.
2. extensions.code: der maschinenlesbare Kern jedes GraphQL-Fehlers
Ein GraphQL-Fehlerobjekt besteht aus message, locations, path und optional extensions. Während die ersten drei Felder von der GraphQL-Engine selbst befüllt werden, liegt extensions vollständig in der Verantwortung der Anwendung. Wer GraphQL Error Codes standardisieren konsequent umsetzt, definiert für jeden fachlichen und technischen Fehlerfall genau einen Code, etwa PRODUCT_NOT_FOUND oder INSUFFICIENT_STOCK, in konstanter SCREAMING_SNAKE_CASE-Schreibweise, damit Codes im gesamten Team eindeutig erkennbar bleiben.
Wichtig ist die Trennung zwischen technischen Standard-Codes wie UNAUTHENTICATED oder BAD_USER_INPUT, die viele GraphQL-Server-Bibliotheken bereits vordefinieren, und fachlichen Codes, die aus der eigenen Domäne stammen. Beide Kategorien gehören in denselben Katalog, aber mit einem klaren Namensraum-Präfix, etwa ORDER_ für Bestellfehler oder PAYMENT_ für Zahlungsfehler, damit auch bei wachsendem Schema keine Namenskollisionen entstehen.
# GraphQL error response with standardized extensions.code
{
"errors": [
{
"message": "The requested product could not be found.",
"locations": [{ "line": 2, "column": 3 }],
"path": ["product"],
"extensions": {
"code": "PRODUCT_NOT_FOUND",
"category": "business",
"productId": "SKU-4821",
"timestamp": "2026-08-06T10:15:00Z"
}
}
],
"data": { "product": null }
}
3. Einen zentralen Error-Code-Katalog aufbauen
Ohne zentrale Registrierungsstelle erfinden verschiedene Teammitglieder unabhängig voneinander Codes für denselben Sachverhalt, OUT_OF_STOCK in einem Resolver, INSUFFICIENT_STOCK in einem anderen. Wer GraphQL Error Codes standardisieren nachhaltig betreiben will, pflegt einen einzigen Enum oder eine einzige Konstanten-Klasse, aus der alle Resolver und Services ihre Codes beziehen. Neue Codes werden per Pull Request in diese zentrale Datei eingetragen, review-pflichtig wie jede andere API-Vertragsänderung.
Dieser Katalog sollte zusätzlich dokumentieren, welcher HTTP-Status-Analogon jedem Code entspricht, ob der Fehler retry-fähig ist, und welche zusätzlichen extensions-Felder er typischerweise mitführt. Diese Metainformation macht den Katalog zur einzigen Quelle der Wahrheit sowohl für Backend-Entwickler, die neue Fehler werfen, als auch für Frontend-Entwickler, die Fehlerfälle in der UI behandeln müssen.
<?php
declare(strict_types=1);
namespace App\GraphQL\Error;
/**
* Central registry of all GraphQL error codes used across the schema.
* Every code must be documented here before it is thrown anywhere.
*/
enum ErrorCode: string
{
case ProductNotFound = 'PRODUCT_NOT_FOUND';
case InsufficientStock = 'INSUFFICIENT_STOCK';
case OrderAlreadyShipped = 'ORDER_ALREADY_SHIPPED';
case PaymentDeclined = 'PAYMENT_DECLINED';
case Unauthenticated = 'UNAUTHENTICATED';
case Forbidden = 'FORBIDDEN';
case BadUserInput = 'BAD_USER_INPUT';
case InternalError = 'INTERNAL_ERROR';
/**
* Returns whether a client may safely retry the operation
* that produced this error code.
*
* @return bool
*/
public function isRetryable(): bool
{
return match ($this) {
self::InternalError => true,
default => false,
};
}
}
4. Domain-Exceptions auf Error Codes abbilden
Resolver sollten niemals direkt GraphQL-Fehlerobjekte konstruieren. Stattdessen wirft die Domänenschicht ihre eigenen, fachlich benannten Exceptions, ProductNotFoundException, InsufficientStockException, unabhängig von GraphQL. Eine zentrale Fehler-Mapping-Schicht übersetzt diese Domain-Exceptions dann in die passenden extensions.code-Werte. Diese Trennung erlaubt, dieselbe Domänenlogik später auch über REST oder CLI zu exponieren, ohne GraphQL-spezifischen Code doppelt zu pflegen.
Wer GraphQL Error Codes standardisieren konsequent umsetzt, implementiert diese Übersetzung als eigenständigen Formatter, der bei jeder Anfrage einmal durchläuft und jede geworfene Exception anhand ihres Typs einem Error Code zuordnet. Unbekannte, nicht gemappte Exceptions fallen niemals mit ihrer internen Nachricht an den Client durch, sondern werden generisch als INTERNAL_ERROR maskiert, während die Originalmeldung nur serverseitig geloggt wird.
<?php
declare(strict_types=1);
namespace App\GraphQL\Error;
use App\Domain\Exception\InsufficientStockException;
use App\Domain\Exception\ProductNotFoundException;
use GraphQL\Error\ClientAware;
use GraphQL\Error\Error;
use Psr\Log\LoggerInterface;
use Throwable;
/**
* Translates domain exceptions into GraphQL errors carrying a
* standardized extensions.code, so resolvers never build error
* objects themselves.
*/
final class DomainErrorFormatter
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
/**
* Maps a caught throwable to a GraphQL error with a stable code.
*
* @param Throwable $exception The exception thrown by domain code
* @return Error GraphQL error carrying extensions.code
*/
public function format(Throwable $exception): Error
{
$code = match (true) {
$exception instanceof ProductNotFoundException => ErrorCode::ProductNotFound,
$exception instanceof InsufficientStockException => ErrorCode::InsufficientStock,
default => null,
};
if ($code === null) {
// Unknown exceptions never leak their internal message
$this->logger->error('Unmapped exception', ['exception' => $exception]);
return new Error('Internal server error.', null, null, [], null, null, [
'code' => ErrorCode::InternalError->value,
]);
}
return new Error($exception->getMessage(), null, null, [], null, $exception, [
'code' => $code->value,
'category' => 'business',
]);
}
}
5. Fehlerkategorien: Validation, Auth, Business, System
Ein flacher Katalog aus fünfzig Einzelcodes wird unübersichtlich, sobald ein Client entscheiden muss, wie generell auf einen unbekannten Code reagiert werden soll. Deshalb ergänzt man extensions.code um extensions.category mit einem festen Wertebereich: validation für fehlerhafte Eingaben, authentication und authorization für Zugriffsprobleme, business für fachliche Regelverletzungen wie nicht ausreichenden Lagerbestand, und system für unerwartete technische Fehler.
Diese Kategorisierung erlaubt Clients, auch auf neue, ihnen unbekannte Codes sinnvoll zu reagieren: Ein Fehler der Kategorie validation kann generisch in einem Formularfeld angezeigt werden, ein Fehler der Kategorie system löst pauschal eine Retry-Logik oder eine Fehlerseite aus, unabhängig vom konkreten Code. Wer GraphQL Error Codes standardisieren so aufbaut, macht das Fehlersystem robust gegen künftige Erweiterungen, ohne dass Clients bei jedem neuen Code aktualisiert werden müssen.
6. Error Formatting: von der Exception zur GraphQL-Response
Die meisten GraphQL-Server-Implementierungen bieten einen zentralen Error-Formatting-Hook, an dem jeder Fehler vor der Auslieferung an den Client passiert, unabhängig davon, in welchem Resolver er entstanden ist. Genau dort, nicht verstreut in einzelnen Resolvern, gehört die in Abschnitt 4 gezeigte Mapping-Logik hin. Diese Zentralisierung stellt sicher, dass kein Resolver versehentlich einen Rohfehler mit Stacktrace-Informationen an den Client durchreicht.
Ein zusätzlicher Aspekt beim Formatting ist die Umgebungsabhängigkeit: In der Entwicklungsumgebung ist es sinnvoll, zusätzliche Debug-Informationen wie Stacktraces in extensions.debugMessage mitzugeben, in Produktion müssen diese Felder vollständig entfernt werden. Wer GraphQL Error Codes standardisieren betreibt, sollte diese Umschaltung über eine einzige Konfigurationsflag steuern, niemals über bedingten Code verstreut in mehreren Formattern.
7. Internationalisierung: Codes stabil, Messages übersetzt
Ein häufiger Fehler ist, die message des GraphQL-Fehlers direkt in der UI anzuzeigen. Das funktioniert, solange die API nur eine Sprache bedient, bricht aber bei jeder Internationalisierung. Die korrekte Trennung: extensions.code bleibt sprachneutral und stabil, die Übersetzung des angezeigten Textes erfolgt vollständig im Client anhand des Codes, meist über eine simple Lookup-Tabelle pro unterstützter Sprache.
Diese Trennung hat einen weiteren Vorteil bei GraphQL Error Codes standardisieren: Das Backend-Team kann Formulierungen jederzeit anpassen, ohne Client-Releases zu erzwingen, solange der Code stabil bleibt. Umgekehrt kann das Frontend-Team eigene, nutzerfreundlichere Texte formulieren, die stärker auf die jeweilige UI-Situation zugeschnitten sind, als es eine generische Backend-Message je leisten könnte.
8. Clientseitige Fehlerbehandlung anhand von Error Codes
Auf Client-Seite lohnt sich ein zentraler Error-Handler, der jede GraphQL-Response nach dem errors-Array durchsucht und anhand von extensions.code in eine passende Behandlung verzweigt, statt dass jede einzelne Komponente eigene Fehlerlogik implementiert. Apollo Client und urql bieten dafür Error-Link- beziehungsweise Exchange-Mechanismen, die zentral vor der eigentlichen Komponente greifen.
Ein robuster Client unterscheidet dabei mindestens drei Reaktionen: automatischer Retry bei retry-fähigen Codes wie temporären Systemfehlern, Redirect zum Login bei UNAUTHENTICATED, und Inline-Anzeige der übersetzten Fehlermeldung bei allen übrigen Codes. Wer GraphQL Error Codes standardisieren auch clientseitig konsequent umsetzt, reduziert die Menge an Spezialfall-Handling in einzelnen UI-Komponenten drastisch.
// Central GraphQL error link: routes errors by extensions.code
// instead of letting every component handle errors individually
import { onError } from "@apollo/client/link/error";
const errorLink = onError(({ graphQLErrors }) => {
graphQLErrors?.forEach((error) => {
const code = error.extensions?.code as string | undefined;
switch (code) {
case "UNAUTHENTICATED":
redirectToLogin();
break;
case "INTERNAL_ERROR":
scheduleRetry(error);
break;
default:
// Unknown or business codes fall back to inline display,
// the message shown is looked up by code, not error.message
showTranslatedError(code ?? "UNKNOWN_ERROR");
}
});
});
9. Fehleransätze im direkten Vergleich
Verschiedene Reifegrade der Fehlerbehandlung führen zu sehr unterschiedlicher Client-Robustheit. Die folgende Tabelle stellt die gängigen Ansätze gegenüber.
| Ansatz | Client-Auswertung | i18n-fähig | Wartbarkeit |
|---|---|---|---|
| Nur message-Text | Fragil, textabhängig | Nein | Niedrig |
| Verstreute Ad-hoc-Codes | Möglich, aber inkonsistent | Teilweise | Mittel |
| Zentraler Katalog + Mapping | Zuverlässig | Ja | Hoch |
| Katalog + Kategorien | Robust gegen neue Codes | Ja | Sehr hoch |
Die Investition in einen zentralen Katalog mit Kategorien zahlt sich spätestens dann aus, wenn mehrere Frontend-Teams gegen dasselbe Schema arbeiten und jedes Team eigene Annahmen über Fehlerformate trifft. Ein dokumentierter, versionierter Katalog verhindert, dass diese Annahmen auseinanderlaufen.
Mironsoft
GraphQL-API-Design, Fehlerbehandlung und Magento-Integration
Ein konsistentes Fehlerformat für euer GraphQL-Schema?
Wir bauen mit euch einen zentralen Error-Code-Katalog, ein sauberes Domain-Exception-Mapping und eine clientseitige Fehlerbehandlung, die auch bei wachsendem Schema vorhersehbar bleibt.
Error-Katalog
Zentrale Registrierung aller extensions.code-Werte mit Kategorien und Retry-Metadaten
Exception-Mapping
Domain-Exceptions sauber auf stabile GraphQL-Fehlercodes abbilden
Client-Integration
Zentrale Error-Links und übersetzte Fehleranzeige auf Basis von Error Codes
10. Zusammenfassung
Wer GraphQL Error Codes standardisieren will, trennt konsequent den menschenlesbaren message-Text von einem stabilen, maschinenlesbaren extensions.code. Ein zentraler Katalog, gepflegt als Enum oder Konstanten-Klasse, verhindert, dass verschiedene Teammitglieder unabhängig voneinander Codes für denselben Sachverhalt erfinden. Domain-Exceptions werden über eine zentrale Formatting-Schicht auf diese Codes gemappt, niemals direkt im Resolver.
Kategorien wie validation, business und system machen das Fehlersystem robust gegen künftige Erweiterungen, weil Clients auch auf unbekannte Codes generisch reagieren können. Internationalisierung gelingt nur, wenn Codes stabil und Messages im Client übersetzt werden, nie umgekehrt. Diese Struktur reduziert Spezialfall-Handling in der UI drastisch und macht Fehlerverhalten über das gesamte System hinweg vorhersehbar.
GraphQL Error Codes standardisieren — Das Wichtigste auf einen Blick
extensions.code
Stabiler, sprachneutraler Code pro Fehlerfall, unabhängig von der menschenlesbaren Message.
Zentraler Katalog
Ein Enum oder eine Konstanten-Klasse als einzige Quelle der Wahrheit für alle Codes im Team.
Exception-Mapping
Zentrale Formatting-Schicht übersetzt Domain-Exceptions in Error Codes, niemals im Resolver selbst.
Kategorien
validation, authentication, business, system erlauben generisches Handling auch unbekannter Codes.