API-Typen automatisch aus OpenAPI/Swagger generieren
<T>
type
TypeScript · OpenAPI · Codegen · CI/CD
API-Typen automatisch aus OpenAPI/Swagger generieren
Wie Frontend und Backend garantiert synchron bleiben

Handgeschriebene TypeScript-Interfaces für REST-Endpunkte veralten schleichend, sobald sich das Backend weiterentwickelt, und liefern dann trügerische Typsicherheit statt echtem Schutz vor Laufzeitfehlern. Dieser Artikel zeigt, wie openapi-typescript und verwandte Tools TypeScript-Typen sowie typisierte Fetch-Clients direkt aus einer OpenAPI-Spezifikation erzeugen und wie sich dieser Prozess zuverlässig in Build- und CI-Pipelines verankern lässt.

13 Min. Lesezeit openapi-typescript · openapi-fetch Magento REST API · GitHub Actions

1. Das Drift-Problem handgeschriebener API-Typen

Das klassische Muster in wachsenden Magento- und Headless-Projekten: Ein Entwickler schreibt zu Projektbeginn ein interface Product { id: number; name: string; price: number; }, weil es genau der aktuellen API-Antwort entspricht. Monate später fügt das Backend-Team ein neues Pflichtfeld hinzu, macht price optional für Draft-Produkte oder benennt ein Feld um. Der handgeschriebene Typ bleibt unverändert, TypeScript kompiliert weiterhin fehlerfrei, weil der Compiler die reale API-Antwort gar nicht kennt - er prüft nur gegen die Behauptung im Interface, nicht gegen die Wahrheit auf dem Server.

Das Ergebnis ist trügerische Typsicherheit: Der Code sieht abgesichert aus, bricht aber zur Laufzeit mit undefined is not an object, sobald ein Feld fehlt, das der Typ als garantiert vorhanden deklariert. Code-Reviews erkennen diese Drift selten, weil niemand bei jeder PR die tatsächliche API-Antwort mit dem Interface abgleicht. Mit der Zeit sinkt das Vertrauen ins Typsystem, Teams beginnen any als Fluchtventil einzusetzen, und genau der Nutzen, für den TypeScript ursprünglich eingeführt wurde, geht schleichend verloren.

2. Was eine OpenAPI-Spezifikation ist und warum sie als Single Source of Truth taugt

OpenAPI (früher Swagger) ist ein maschinenlesbares YAML- oder JSON-Format zur Beschreibung von REST-APIs: Pfade, HTTP-Methoden, Parameter, Request- und Response-Schemas sowie Authentifizierung werden vollständig deklarativ erfasst. Viele Backend-Frameworks generieren diese Spec automatisch aus Code-Annotationen statt sie manuell zu pflegen - Symfony API Platform, NestJS mit @nestjs/swagger, Laravel mit L5-Swagger, und auch Magentos eigene REST-API liefert eine vollständige Swagger-Definition unter /rest/V1/schema beziehungsweise /swagger aus.

Wird die Spec direkt aus dem Backend-Code generiert, kann sie strukturell nicht veralten: Jede Änderung an einem Controller oder DTO spiegelt sich automatisch in der nächsten Spec-Generierung wider. Damit wird die OpenAPI-Datei zum verbindlichen Vertrag zwischen Backend- und Frontend-Team, und genau dieser Vertrag lässt sich als Eingabe für die TypeScript-Typgenerierung nutzen, statt Typen ein zweites Mal von Hand nach der Dokumentation abzutippen.


openapi: 3.1.0
info:
  title: Mironsoft Product API
  version: "1.2.0"
paths:
  /products/{sku}:
    get:
      operationId: getProductBySku
      parameters:
        - name: sku
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: A single product
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
        "404":
          description: Product not found
components:
  schemas:
    Product:
      type: object
      required:
        - id
        - sku
        - name
        - price
      properties:
        id:
          type: integer
        sku:
          type: string
        name:
          type: string
        price:
          type: number
          format: float
        specialPrice:
          type: number
          format: float
          nullable: true
        tags:
          type: array
          items:
            type: string

3. openapi-typescript: reine Typen ohne Laufzeit-Overhead

openapi-typescript liest eine OpenAPI-Datei, egal ob als lokale YAML/JSON-Datei oder per URL von einer laufenden Instanz abgerufen, und erzeugt daraus eine einzige .d.ts-Datei mit einem paths-Interface für alle Endpunkte sowie einem components["schemas"]-Interface für alle Datenmodelle. Entscheidend ist, dass das Tool ausschließlich Typen erzeugt, keine Laufzeit-Bibliothek: Es entstehen keine zusätzlichen Bytes im Produktions-Bundle, weil Typinformationen beim Kompilieren restlos entfernt werden. Das unterscheidet es von schwergewichtigeren Codegen-Ansätzen, die vollständige Client-Klassen mit eigener Laufzeitlogik erzeugen.

In der Praxis reicht ein einziger Befehl: npx openapi-typescript ./openapi/product-api.yaml -o ./src/api/schema.d.ts. Ein Watch-Modus regeneriert die Typen automatisch bei jeder Änderung der lokalen Spec-Datei, was sich besonders während der gemeinsamen Backend-Frontend-Entwicklung auszahlt. Wird die Spec stattdessen live von einer laufenden Magento-Instanz per URL gezogen, spiegeln die generierten Typen exakt den Zustand der tatsächlich erreichbaren API wider, nicht nur eine irgendwann eingecheckte Momentaufnahme.


// Auto-generated by openapi-typescript. DO NOT EDIT BY HAND.
export interface paths {
  "/products/{sku}": {
    get: operations["getProductBySku"];
  };
}

export interface components {
  schemas: {
    Product: {
      id: number;
      sku: string;
      name: string;
      price: number;
      /** @description Nullable in the spec, so number | null in TS */
      specialPrice: number | null;
      tags?: string[];
    };
  };
}

export interface operations {
  getProductBySku: {
    parameters: {
      path: { sku: string };
    };
    responses: {
      200: {
        content: { "application/json": components["schemas"]["Product"] };
      };
      404: {
        content: never;
      };
    };
  };
}

4. Einen typisierten Fetch-Client auf den generierten Typen aufbauen

Reine Typen allein verhindern noch keine Laufzeitfehler - erst ein Client, der diese Typen tatsächlich durchsetzt, schließt den Kreis. openapi-fetch, aus derselben Werkzeugfamilie wie openapi-typescript, ist ein wenige Kilobyte großer Wrapper um die native fetch-API, der Pfade, HTTP-Methode, Parameter und Response-Body vollständig gegen das generierte paths-Interface prüft. Die IDE bietet dabei Autovervollständigung für gültige Pfade und Parameter, und ein Tippfehler im Endpunkt-Namen wird zum Compile-Fehler statt zu einem stillen 404 zur Laufzeit.

Wer keine zusätzliche Abhängigkeit im Bundle haben möchte, kann denselben Effekt mit einem schlanken, selbst geschriebenen Generic-Wrapper um fetch erreichen, der die generierten operations-Typen als Typparameter nutzt. Wichtig bleibt in beiden Fällen: Die Typprüfung findet ausschließlich zur Compile-Zeit statt. Ob die Response zur Laufzeit tatsächlich dem Schema entspricht, prüft weder openapi-typescript noch openapi-fetch - dafür braucht es bei Bedarf eine zusätzliche Laufzeitvalidierung, etwa mit zod.


import createClient from "openapi-fetch";
import type { paths } from "./api/schema";

// The client is fully typed against the generated `paths` interface
const client = createClient<paths>({ baseUrl: "https://shop.example.com" });

async function loadProduct(sku: string) {
  const { data, error } = await client.GET("/products/{sku}", {
    params: { path: { sku } },
  });

  if (error) {
    // error is typed based on the non-2xx responses in the spec
    console.error("Product lookup failed", error);
    return null;
  }

  // data.price is a number, data.specialPrice is number | null -
  // both checked at compile time against the OpenAPI schema
  return data;
}

5. Versionierte APIs und mehrere Specs in einem Projekt verwalten

Reale Magento-Projekte sprechen selten nur mit einer einzigen API. Typisch ist eine Kombination aus der Magento REST API für Katalog, Warenkorb und Kunde, plus einem oder mehreren internen Microservices, etwa für eine eigene Preisengine oder eine externe Suche. Jede dieser Quellen bekommt eine eigene generierte Typdatei mit eindeutigem Namen, zum Beispiel schema-magento.d.ts und schema-pricing.d.ts, damit gleichnamige Schema-Typen wie Product aus unterschiedlichen Services nicht kollidieren und über getrennte Namespace-Importe eindeutig referenzierbar bleiben.

Für Versionierung gilt ein ähnliches Prinzip: Führt das Backend eine Breaking-Change-Version v2 ein, wird zunächst eine zweite Spec parallel zur bestehenden v1-Spec generiert, sodass beide Typversionen koexistieren, bis die Migration im Frontend abgeschlossen ist. Klar benannte npm-Skripte pro Spec, etwa generate:magento und generate:pricing, halten diesen Prozess nachvollziehbar und verhindern, dass ein einzelner globaler Codegen-Befehl unübersichtlich wird, sobald ein Projekt über drei oder vier Backends hinweg wächst.

6. Codegen in npm-Skripte und CI verankern

Ohne Automatisierung verkommt Typgenerierung zu einem manuellen Schritt, den irgendwann jemand vergisst. Die einfachste Absicherung sind prebuild- und predev-Hooks in package.json, die den Codegen-Befehl automatisch vor jedem Build beziehungsweise Entwicklungsstart ausführen. So können lokal entwickelte Typen nie länger als eine Session veralten, weil sie bei jedem Start neu aus der aktuellen Spec gezogen werden.

In der CI-Pipeline kommt ein zweiter, härterer Schritt hinzu: die Spec wird frisch vom Backend abgerufen, die Typen werden neu generiert, und anschließend prüft git diff --exit-code die generierten Dateien gegen den committeten Stand im Repository. Weicht das Ergebnis ab, schlägt der Build fehl - ein klares Signal, dass sich das Backend-Schema geändert hat, ohne dass die Frontend-Typen regeneriert und committet wurden. Dieser Schema-Drift-Check macht aus einem stillen, schleichenden Problem einen lauten, sofort sichtbaren CI-Fehler.


{
  "scripts": {
    "generate:api-types": "openapi-typescript ./openapi/product-api.yaml -o ./src/api/schema.d.ts",
    "generate:pricing-types": "openapi-typescript ./openapi/pricing-api.yaml -o ./src/api/pricing-schema.d.ts",
    "prebuild": "npm run generate:api-types && npm run generate:pricing-types",
    "predev": "npm run generate:api-types && npm run generate:pricing-types",
    "build": "vite build"
  }
}

- name: Regenerate API types and check for drift
  run: |
    npm run generate:api-types
    npm run generate:pricing-types
    # Fail the build if the freshly generated types differ from
    # what is committed in the repository - this is schema drift.
    git diff --exit-code -- src/api/schema.d.ts src/api/pricing-schema.d.ts

- name: Fail with a clear message on drift
  if: failure()
  run: |
    echo "Generated API types are out of sync with the OpenAPI spec."
    echo "Run 'npm run generate:api-types' locally and commit the result."
    exit 1

7. Breaking-Change-Erkennung zwischen alten und neuen Typen

Weil die generierte .d.ts-Datei deterministisch und für Menschen lesbar ist, wird ein einfacher git diff auf diese Datei zu einem überraschend wirksamen Werkzeug: Ein entferntes Property, ein von string zu string | null geändertes Feld oder ein neues Pflichtfeld tauchen im Pull-Request-Diff exakt dort auf, wo sie hingehören - sichtbar, kommentierbar, reviewbar. Bei handgeschriebenen Typen passiert dieselbe Änderung oft still, weil ein Entwickler den Typ "nebenbei" an die neue Realität anpasst, ohne dass der eigentliche Auslöser im Backend jemals im Diff sichtbar wird.

Für noch präzisere Aussagen lassen sich dedizierte Diff-Tools wie oasdiff oder openapi-diff einsetzen, die zwei Spec-Versionen semantisch vergleichen und Änderungen automatisch als breaking oder non-breaking klassifizieren - ein neues optionales Feld gilt als sicher, ein entferntes oder neu verpflichtendes Feld als Breaking Change. Die Kombination aus generiertem Typ-Diff und semantischem Spec-Diff gibt in der CI-Pipeline eine doppelte Absicherung: Das eine zeigt die konkrete TypeScript-Auswirkung, das andere die fachliche Schwere der Änderung.

8. Grenzen und Fallstricke: unvollständige Specs und Nullable-Ambiguitäten

Codegen ist nur so vertrauenswürdig wie die zugrunde liegende Spec selbst. Wird eine Swagger-Datei von Hand gepflegt statt aus dem Code generiert, driftet sie mit der Zeit genauso wie handgeschriebene Typen, nur eine Ebene höher im Stack. Fehlt in der Spec ein korrektes required-Array, markiert openapi-typescript fast alle Felder als optional, was TypeScript zwingt, überall Optional-Chaining zu verwenden, selbst dort, wo ein Feld in der Praxis immer vorhanden ist - ein Vorteil an Sicherheit erkauft mit unnötig sperrigem Code.

Auch nullable-Semantik ist zwischen Frameworks nicht einheitlich: Manche Backends geben ein fehlendes Feld zurück, andere null, wieder andere einen Platzhalter wie "" oder 0, und all das kann unterschiedlich in die Spec übersetzt werden. Erschwerend kommt hinzu, dass OpenAPI 3.0 und 3.1 nullable syntaktisch verschieden behandeln: 3.0 nutzt nullable: true, 3.1 folgt dem JSON-Schema-Standard mit type: ["string", "null"]. Ein sauberer Codegen-Workflow ersetzt daher nicht das Gespräch mit dem Backend-Team über ein gemeinsames Verständnis von Pflichtfeldern.

9. Handgeschriebene vs. generierte API-Typen im Vergleich

Die folgende Tabelle fasst zusammen, worin sich handgeschriebene und aus OpenAPI generierte TypeScript-Typen in der Praxis unterscheiden - über die reine Anfangsersparnis hinaus, die beim ersten Anlegen eines Interfaces oft täuschend gering wirkt.

Kriterium Handgeschriebene Typen Generierte Typen
Genauigkeit über Zeit Driftet unbemerkt bei jeder Backend-Änderung Immer synchron mit der aktuellen Spec
Pflegeaufwand Manuell pro Endpunkt, skaliert schlecht Ein Befehl für alle Endpunkte gleichzeitig
Onboarding-Geschwindigkeit Neue Entwickler lesen Docs und raten Felder IDE-Autovervollständigung direkt aus der Spec
Breaking-Change-Erkennung Fällt erst zur Laufzeit auf Sichtbar im Diff, blockierbar in CI
Nullable-Handling Abhängig von der Disziplin des Entwicklers Direkt aus dem Schema abgeleitet, konsistent

Der Vergleich zeigt kein Nischenwerkzeug für Großprojekte, sondern einen strukturellen Vorteil, der sich bereits ab wenigen Dutzend Endpunkten auszahlt. Generierte Typen verschieben Fehler konsequent von der Laufzeit in die Compile-Zeit oder sogar in die CI-Pipeline, lange bevor Code überhaupt in Produktion läuft.

Mironsoft

TypeScript-Codegen und typsichere API-Integrationen für Magento- und Headless-Projekte

API-Typen zuverlässig synchron halten?

Wir richten openapi-typescript und typisierte Fetch-Clients für eure Magento REST API und interne Microservices ein und verankern Schema-Drift-Checks fest in eurer CI-Pipeline, damit veraltete API-Typen erst gar nicht mehr entstehen.

OpenAPI-Codegen-Setup

Typgenerierung aus Magento- und Microservice-Specs einrichten

CI-Pipeline-Integration

Schema-Drift-Checks und Breaking-Change-Diffs in GitHub Actions

Typisierte API-Clients

openapi-fetch-Wrapper mit optionaler Laufzeitvalidierung via zod

10. Zusammenfassung

API-Typen automatisch aus OpenAPI/Swagger zu generieren löst ein Problem, das handgeschriebene Interfaces strukturell nicht lösen können: garantierte Übereinstimmung zwischen dem, was der Typ verspricht, und dem, was das Backend tatsächlich liefert. openapi-typescript erzeugt reine, laufzeitfreie Typen direkt aus der Spec, openapi-fetch oder ein schlanker eigener Wrapper macht diese Typen an jedem API-Aufruf tatsächlich wirksam, und Schema-Drift-Checks in der CI-Pipeline verhindern, dass veraltete Typen überhaupt gemergt werden können.

Der entscheidende Hebel liegt nicht im einzelnen Codegen-Befehl, sondern in der Automatisierung: prebuild-Hooks für die lokale Entwicklung und ein harter git diff --exit-code-Schritt in CI sorgen dafür, dass Frontend-Typen niemals mehr als einen Build-Zyklus hinter dem echten API-Vertrag zurückfallen. Bleiben die Grenzen des Ansatzes im Blick, allen voran unvollständige oder handgepflegte Specs und uneinheitliche Nullable-Semantik, wird aus Typgenerierung ein verlässlicher, fast unsichtbarer Teil der täglichen Entwicklungsroutine.

API-Typen aus OpenAPI generieren - Das Wichtigste auf einen Blick

Single Source of Truth

Die OpenAPI-Spec, idealerweise aus Backend-Code generiert, ersetzt handgeschriebene Interfaces als verbindlichen Vertrag.

Zero-Runtime-Typen

openapi-typescript erzeugt reine .d.ts-Dateien ohne zusätzliches Bundle-Gewicht in Produktion.

CI Drift-Check

git diff --exit-code auf frisch generierte Typen macht Schema-Drift zu einem sofort sichtbaren Build-Fehler.

Breaking-Change-Diff

Typ-Diffs und semantische Spec-Diffs wie oasdiff zeigen Änderungen, bevor sie das Frontend brechen.

11. FAQ: API-Typen aus OpenAPI generieren

1Was ist openapi-typescript und wie unterscheidet es sich von anderen Codegen-Tools?
Erzeugt ausschließlich TypeScript-Typdefinitionen ohne Laufzeit-Bibliothek. Andere Tools generieren zusätzlich vollständige Client-Klassen mit eigener Laufzeitlogik und mehr Bundle-Gewicht.
2Brauche ich openapi-fetch oder reicht natives fetch mit den generierten Typen?
Natives fetch prüft Pfade und Response-Typen nicht automatisch. openapi-fetch stellt diese Verbindung typsicher her, ist aber kein Muss - ein eigener Generic-Wrapper funktioniert ebenso.
3Wie halte ich generierte Typen mit einer laufenden Magento-Instanz synchron?
Spec direkt per URL von der Magento-REST-Schema-Route laden. Ein prebuild- oder predev-Hook zieht die Typen bei jedem Build oder Entwicklungsstart neu.
4Was passiert, wenn die OpenAPI-Spec selbst fehlerhaft oder unvollständig ist?
Fehlende required-Arrays führen zu unnötig vielen optionalen Feldern. Eine handgepflegte, nicht aus Code generierte Spec driftet mit der Zeit genauso wie handgeschriebene Typen.
5Wie erkenne ich Breaking Changes in der API, bevor sie das Frontend brechen?
Ein git diff auf die generierte Typdatei zeigt strukturelle Änderungen im Pull Request. Semantische Diff-Tools wie oasdiff klassifizieren Änderungen zusätzlich als breaking oder non-breaking.
6Kann ich mehrere OpenAPI-Specs in einem Projekt kombinieren?
Ja. Jede Spec wird in eine eigene, klar benannte Typdatei generiert und über getrennte Namespace-Importe verwendet, um Typkollisionen zu vermeiden.
7Lohnt sich Codegen für kleine Projekte mit wenigen Endpunkten?
Ja, der Einrichtungsaufwand ist gering und der Schutz vor Drift greift schon ab dem ersten Backend-Update. Der Vorteil wächst mit der Endpunktzahl, ist aber an keine Mindestgröße gebunden.
8Wie unterscheiden sich OpenAPI 3.0 und 3.1 bei nullable Feldern?
3.0 nutzt nullable: true. 3.1 folgt dem JSON-Schema-Standard und drückt Nullable-Felder über type: ["string", "null"] aus. Codegen-Tools müssen beide Schreibweisen korrekt verarbeiten.
9Ersetzt der generierte Typ die Laufzeitvalidierung mit zod oder io-ts?
Nein. Generierte Typen wirken nur zur Compile-Zeit. Für echte Laufzeitsicherheit bleibt eine zusätzliche Validierung mit zod sinnvoll, besonders bei nicht vertrauenswürdigen Drittanbieter-APIs.
10Wie verhindere ich, dass generierte Typen versehentlich manuell editiert werden?
Ein Kommentar-Header "Auto-generated, DO NOT EDIT BY HAND" signalisiert die Regel. Der CI-Drift-Check macht sie verbindlich: manuelle Änderungen werden beim nächsten Codegen-Lauf überschrieben.