für TypeScript und PHP richtig nutzen
Wer GraphQL-Queries manuell mit Typen synchron hält, baut technische Schulden auf – jeden Mal, wenn sich ein Schema-Feld ändert. Code-Generation aus Schema und Operations-Dateien macht Typen zur Automatisierung statt zur Wartungsaufgabe.
Inhaltsverzeichnis
- 1. Warum manuelle Typen bei GraphQL ein Anti-Pattern sind
- 2. graphql-codegen: Grundprinzip und Workflow
- 3. TypeScript-Plugins: typed-document-node, react-query und urql
- 4. PHP-Code-Generation: Interfaces und Value Objects aus dem Schema
- 5. Konfiguration: codegen.ts sauber strukturieren
- 6. Magento-Schema als Codegen-Quelle nutzen
- 7. CI-Integration: Generated Code prüfen ohne ihn einzuchecken
- 8. Codegen-Ansätze im Vergleich
- 9. Typische Fehler bei der Code-Generation
- 10. Zusammenfassung
- 11. FAQ
1. Warum manuelle Typen bei GraphQL ein Anti-Pattern sind
GraphQL liefert ein typisiertes Schema: Jedes Feld, jeder Typ und jede Operation ist eindeutig definiert. Trotzdem ist es weit verbreitet, TypeScript-Interfaces für GraphQL-Responses manuell zu pflegen – parallel zum Schema, das sich unabhängig davon weiterentwickelt. Das führt zu einem systematischen Desynchronisierungsproblem: Sobald ein Schema-Feld umbenannt oder ein Typ erweitert wird, müssen alle manuell gepflegten Interfaces manuell nachgezogen werden. In einem Team mit mehreren Entwicklern und einem aktiv entwickelten Schema ist das kein Einzelfall, sondern Dauerbetrieb.
Das Problem ist nicht auf TypeScript beschränkt. Auch PHP-Code, der GraphQL-Responses verarbeitet – sei es in einem Headless-Frontend-BFF, einem CLI-Tool oder einem API-Integration-Layer – arbeitet ohne Code-Generation mit impliziten Array-Strukturen, bei denen ein Tippfehler im Feldnamen erst zur Laufzeit auffällt. Code-Generation aus dem Schema macht Typsicherheit zur Norm statt zur manuellen Disziplin.
2. graphql-codegen: Grundprinzip und Workflow
Das Tool @graphql-codegen/cli liest das GraphQL-Schema (als SDL-Datei, URL oder Introspection-Result) und die Operations-Dateien (Queries, Mutations, Fragments) und erzeugt daraus Code nach konfigurierbaren Plugins. Der Workflow ist einfach: Schema und Operations liegen im Repository, graphql-codegen wird als Build-Step ausgeführt und erzeugt eine oder mehrere Output-Dateien. Der generierte Code wird entweder eingecheckt oder ausschließlich in CI als Verifikationsschritt genutzt.
Die Konfiguration erfolgt in einer codegen.ts-Datei im Projekt-Root. Sie definiert, wo das Schema liegt, welche Documents (Operations-Dateien) eingelesen werden und welche Plugins mit welchen Einstellungen für welche Output-Dateien ausgeführt werden. Die Stärke des Tools liegt in der Plugin-Architektur: Für unterschiedliche Frameworks – React, Vue, urql, react-query – gibt es spezialisierte Plugins, die nicht nur TypeScript-Typen, sondern komplett typisierte Hooks erzeugen.
# Operations file: src/graphql/customer.graphql
# graphql-codegen reads this alongside the schema to generate typed hooks
query GetCustomer {
customer {
firstname
lastname
email
addresses {
id
street
city
postcode
country_code
}
}
}
mutation UpdateCustomerEmail($email: String!, $password: String!) {
updateCustomerEmail(email: $email, password: $password) {
customer {
email
}
}
}
# Fragment reuse — codegen generates fragment types automatically
fragment CustomerBasic on Customer {
firstname
lastname
email
}
3. TypeScript-Plugins: typed-document-node, react-query und urql
Das Plugin @graphql-codegen/typed-document-node erzeugt typisierte DocumentNode-Objekte, die direkt an Apollo Client, urql oder fetch-basierte Clients übergeben werden können. Das Ergebnis: Der Compiler kennt den exakten Typ des Response-Objekts, inklusive aller verschachtelten Felder und optionalen Nullable-Typen. Tippfehler bei Feldnamen werden sofort zur Compile-Zeit erkannt, nicht erst zur Laufzeit in der Produktion.
Für react-query erzeugt das Plugin @graphql-codegen/typescript-react-query fertige, typisierte useQuery- und useMutation-Hooks, die direkt im Component-Code verwendet werden können. Der Developer importiert den generierten Hook, übergibt die Variablen und erhält ein vollständig typisiertes Response-Objekt – ohne eine Zeile TypeScript-Interface selbst schreiben zu müssen. Das Plugin für urql funktioniert analog mit useQuery- und useMutation-Komposables, die dem urql-API entsprechen.
4. PHP-Code-Generation: Interfaces und Value Objects aus dem Schema
Für PHP existieren weniger etablierte Codegen-Tools als für TypeScript, aber der Ansatz ist derselbe. Ein PHP-GraphQL-Client, der Responses verarbeitet, profitiert von generierten Value-Objects, die das Schema widerspiegeln. Tools wie spawnia/sailor oder softonic/graphql-client mit eigenem Codegen-Layer erzeugen PHP-Klassen aus SDL-Definitionen, die Responses direkt deserialisieren und als typisierte Objekte zurückgeben. Das eliminiert die typische $response['data']['customer']['email']-Kette zugunsten von $response->customer->email mit IDE-Autocomplete.
In Magento selbst wird Code-Generation auf der Server-Seite nicht eingesetzt – die Resolver-Architektur von Magento ist nicht von einem externen Schema abhängig, das generiert werden müsste. Aber in einem Headless-Setup, wo ein PHP-BFF das Magento-GraphQL-Schema konsumiert, kann PHP-Codegen erheblichen Mehrwert liefern. Das Magento-Schema wird per Introspection abgerufen, als SDL-Datei gespeichert und dann als Input für den PHP-Codegen-Prozess genutzt.
# codegen.ts — configuration for TypeScript + PHP generation
# TypeScript output: typed document nodes + react-query hooks
# generates: src/generated/graphql.ts
#
# documents: 'src/graphql/**/*.graphql'
# schema: https://shop.example.com/graphql
# plugins:
# - typescript
# - typescript-operations
# - typed-document-node
# - typescript-react-query
# Usage in React component (generated hook):
# import { useGetCustomerQuery } from '../generated/graphql'
#
# const { data, isLoading } = useGetCustomerQuery()
# data?.customer?.email // fully typed, nullable-aware
# PHP output (sailor pattern): generates PHP value objects
# CustomerResponse::class with ->customer->email typed as string|null
5. Konfiguration: codegen.ts sauber strukturieren
Eine sinnvolle codegen.ts-Konfiguration trennt Schema-Typen von Operations-Typen und ermöglicht mehrere Output-Dateien für unterschiedliche Schichten. Der häufigste Fehler: alle Plugins in eine einzige Output-Datei zu schreiben, die dann als monolithisches File hunderte von generierten Typen enthält. Besser ist eine Aufteilung in eine Basis-Typen-Datei (aus dem Schema, ohne Operations) und Operations-spezifische Dateien, die Fragment-Typen und Hooks für einzelne Domänen enthalten.
Skalare müssen in der Konfiguration explizit gemappt werden. Das Magento-Schema enthält Custom Scalars wie String für Preise (die intern als Float behandelt werden) und Boolean. Ohne explizites Scalar-Mapping erzeugt codegen für unbekannte Skalare den Typ any – was den Typ-Sicherheitsgewinn zunichte macht. Ein guter Ansatz: alle Custom Scalars auf konkrete TypeScript-Typen oder Branded Types mappen, die versehentliche Verwechslungen zwischen ähnlichen Feldern verhindern.
6. Magento-Schema als Codegen-Quelle nutzen
Das Magento-GraphQL-Schema kann über den Introspection-Endpunkt als SDL-Datei exportiert werden und als Schema-Quelle für graphql-codegen dienen. Das ist sinnvoll für Headless-Setups, wo das Frontend direkt gegen das Magento-Schema entwickelt. Der Workflow: Schema per Introspection in CI abrufen, als schema.graphql speichern und in codegen als Quelle angeben. So ist das generierte Typsystem immer synchron mit dem tatsächlichen Magento-Schema.
Ein praktisches Problem dabei: Das Magento-Schema ist sehr groß und enthält hunderte von Typen, die ein durchschnittliches Frontend-Projekt nicht benötigt. Die graphql-codegen-Option onlyOperationTypes: true begrenzt die generierten Typen auf die Typen, die tatsächlich in den Operations verwendet werden – das reduziert die Output-Größe erheblich und macht die generierte Datei wartbarer. Für Magento-Projekte mit vielen Modulen lohnt es sich außerdem, das Schema lokal zu cachen statt bei jedem codegen-Aufruf neu per Introspection abzurufen.
7. CI-Integration: Generated Code prüfen ohne ihn einzuchecken
Ob generierter Code ins Repository eingecheckt werden soll, ist eine Teamentscheidung. Für eingecheckten Code gilt: Der npm run codegen-Befehl muss in CI ausgeführt werden, und ein git diff --exit-code danach stellt sicher, dass der eingecheckte Code mit dem generierten Code übereinstimmt. Weicht der eingecheckte Code ab, schlägt der CI-Step fehl und signalisiert, dass Operations geändert wurden ohne die Typen zu regenerieren.
Alternativ kann generierter Code komplett aus dem Repository ausgeschlossen und nur in CI erzeugt werden. Das vermeidet Merge-Konflikte in generierten Dateien, erfordert aber einen Build-Step vor der Entwicklung. In der Praxis ist der erste Ansatz (eingecheckt + CI-Check) für Teams einfacher, weil IDE-Autocomplete sofort funktioniert und keine lokale Build-Infrastruktur für neue Team-Mitglieder erforderlich ist. Wichtig in beiden Fällen: graphql-codegen mit dem Flag --check in CI ausführen, das den Prozess mit Exit-Code 1 abbricht, wenn Output-Dateien sich ändern würden.
# CI pipeline step — verify generated types are up to date
# Run codegen in check mode: fails if output would change
# package.json scripts:
# "codegen": "graphql-codegen --config codegen.ts"
# "codegen:check": "graphql-codegen --config codegen.ts --check"
# CI YAML (GitHub Actions pattern):
# - name: Check GraphQL types are up to date
# run: npm run codegen:check
# # Fails with exit code 1 if schema or operations changed
# # without regenerating the typed files
# Schema introspection for Magento:
query IntrospectSchema {
__schema {
types {
name
kind
fields {
name
type { name kind }
}
}
}
}
# Export schema to SDL file (alternative to introspection at runtime):
# npx get-graphql-schema https://shop.example.com/graphql > schema.graphql
8. Codegen-Ansätze im Vergleich
Unterschiedliche Setups erfordern unterschiedliche Codegen-Strategien. Die Wahl des richtigen Ansatzes hängt von Framework, Team-Größe und Schema-Komplexität ab.
| Ansatz | Framework | Output | Besonderheit |
|---|---|---|---|
| typed-document-node | Apollo, urql, fetch | DocumentNode + Typen | Framework-agnostisch, minimal |
| typescript-react-query | React + react-query | Typisierte useQuery-Hooks | Keine manuelle Hook-Schicht nötig |
| typescript-urql | Vue, React + urql | Typisierte Composables | Gut für Vue-Setups |
| spawnia/sailor (PHP) | PHP BFF/CLI | PHP Value Objects | Typsichere PHP-Clients |
| Manuelle Typen | Beliebig | Handgeschriebene Interfaces | Desynchronisiert bei Schema-Änderungen |
In Magento-Headless-Projekten mit React-Frontend ist typed-document-node kombiniert mit einem fetch-basierten Client oft die wartbarste Wahl, weil sie keine Apollo-Abhängigkeit erfordert. Für Teams, die bereits react-query nutzen, liefert das react-query-Plugin den höchsten Produktivitätsvorteil durch komplett fertige Hooks. PHP-Codegen ist sinnvoll, sobald ein PHP-Layer das Magento-GraphQL-Schema konsumiert.
9. Typische Fehler bei der Code-Generation
Der häufigste Fehler ist das Ignorieren von Nullable-Typen im generierten Code. GraphQL-Typen sind standardmäßig nullable, was im generierten TypeScript zu vielen string | null | undefined-Typen führt. Teams, die das als störend empfinden und die Typen in manuelle Interfaces überführen, verlieren den Wartbarkeitsvorteil der Codegen. Die richtige Reaktion: den optionalen Chaining-Operator (?.) konsequent nutzen und Null-Checks explizit modellieren, statt die Nullability durch Typ-Casts wegzudenken.
Ein zweiter verbreiteter Fehler ist, Fragments nicht zu nutzen. Ohne Fragments erzeugt codegen für identische Feldmengen in verschiedenen Queries separate Typen ohne Wiederverwendung. Mit Fragments wird ein CustomerBasicFragment-Typ einmal generiert und in allen Queries, die dieses Fragment verwenden, als gemeinsamer Basis-Typ eingesetzt. Das macht den generierten Code konsistenter und macht refactoring einfacher, weil eine Fragment-Änderung automatisch alle abgeleiteten Typen aktualisiert.
# WRONG — duplicate field sets without fragment, generates separate types
query GetCustomerForHeader {
customer { firstname lastname email }
}
query GetCustomerForProfile {
customer { firstname lastname email addresses { city } }
}
# RIGHT — shared fragment, codegen generates reusable CustomerBasicFragment type
fragment CustomerBasic on Customer {
firstname
lastname
email
}
query GetCustomerForHeader {
customer { ...CustomerBasic }
}
query GetCustomerForProfile {
customer {
...CustomerBasic
addresses { city postcode }
}
}
# Generated TypeScript (simplified):
# type CustomerBasicFragment = { firstname: string; lastname: string; email: string }
# type GetCustomerForProfileQuery = { customer: CustomerBasicFragment & { addresses: ... } }
10. Zusammenfassung
GraphQL Code Generation löst ein systematisches Problem: Manuelle Typen sind immer eine Schritt hinter dem Schema. graphql-codegen macht aus diesem manuellen Prozess einen automatisierten Build-Step, der typsichere TypeScript-Hooks und PHP-Value-Objects direkt aus Schema und Operations erzeugt. Der Aufwand für die initiale Konfiguration amortisiert sich schon nach den ersten Schema-Änderungen, weil kein Entwickler mehr manuell prüfen muss, ob Typen noch aktuell sind.
Für Magento-Headless-Projekte ist das Magento-Schema als codegen-Quelle besonders wertvoll, weil das Schema groß und häufig durch Modul-Updates erweitert wird. CI-Integration mit --check-Flag stellt sicher, dass Teams nicht versehentlich veraltete Typen produktiv bringen. Die Kombination aus typisierte-document-node-Outputs und konsequentem Fragment-Einsatz ergibt den kompaktesten und wartbarsten generierten Code.
GraphQL Code Generation — Das Wichtigste auf einen Blick
graphql-codegen
Liest Schema + Operations, erzeugt typisierte TypeScript-Hooks und PHP-Value-Objects. Plugin-Architektur für framework-spezifische Outputs.
CI-Integration
graphql-codegen --check als CI-Step – schlägt fehl, wenn Schema oder Operations geändert wurden ohne Typen zu regenerieren.
Fragments nutzen
Fragments erzeugen wiederverwendbare Basis-Typen statt duplizierter Interfaces. Eine Fragment-Änderung aktualisiert automatisch alle abhängigen Typen.
Nullable-Typen
GraphQL-Nullability in TypeScript nicht wegcasten. Optional Chaining (?.) konsequent nutzen, Null-Checks explizit modellieren.