GraphQL Error Codes standardisieren: strukturierte Fehlerantworten
AI generated
{ }
type
GraphQL · Error Handling · API-Design · PHP
GraphQL Error Codes standardisieren
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.

17 Min. Lesezeit extensions.code · Error-Katalog · Exception-Mapping GraphQL 16 · PHP 8.4 · API-Design

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.

11. FAQ: GraphQL Error Codes standardisieren

1Warum reicht message allein nicht?
Formulierungen können sich ändern, ohne dass sich die Fehlerursache ändert. Ein stabiler Code ist die einzig verlässliche Grundlage.
2Was gehört in extensions.code?
Ein stabiler, sprachneutraler String in SCREAMING_SNAKE_CASE, der den Fehlerfall eindeutig identifiziert.
3Doppelte Codes vermeiden?
Ein zentraler Katalog als Enum oder Konstanten-Klasse dient als einzige Quelle der Wahrheit, neue Codes per Pull Request.
4Resolver erzeugen Fehlerobjekte?
Nein, Resolver werfen Domain-Exceptions, eine zentrale Formatting-Schicht übersetzt diese in GraphQL-Fehler.
5Unbekannte Exceptions behandeln?
Generisch als INTERNAL_ERROR maskieren, interne Nachricht nur serverseitig loggen, niemals an den Client.
6Wozu dient extensions.category?
Erlaubt generisches Handling auch unbekannter, künftiger Codes anhand ihrer Kategorie.
7Internationalisierung von Fehlern?
Code bleibt stabil, Übersetzung erfolgt vollständig im Client anhand des Codes.
8Debug-Infos wie Stacktraces?
Nur in Entwicklung über zentrale Flag, in Produktion vollständig entfernen.
9Client-Reaktion auf Error Codes?
Zentraler Error-Handler mit Verzweigung nach Code: Retry, Redirect oder übersetzte Inline-Anzeige.
10Ab wann lohnt sich der Katalog?
Spätestens sobald mehrere Frontend-Teams gegen dasselbe Schema arbeiten und eigene Annahmen treffen würden.