{ }
GET
OpenAPI · Codegen · openapi-generator · CI/CD
OpenAPI Codegen: Clients und Server-Stubs ohne Müll erzeugen
sauberer generierter Code ohne manuelle Nacharbeit

Code-Generierung aus OpenAPI-Spezifikationen klingt verlockend, produziert in der Praxis aber oft unlesbaren Müll, der sofort manuell gepatcht werden muss. Das zerstört den Hauptvorteil der Generierung. Dieser Artikel zeigt, wie man openapi-generator so konfiguriert, dass der Ausgabecode wartbar ist – und warum Template-Overrides der Schlüssel dazu sind.

16 Min. Lesezeit openapi-generator · Template-Overrides · CI-Integration · Versionierung PHP · TypeScript · Go · OpenAPI 3.1

1. Warum Codegen scheitert und warum es trotzdem lohnt

Das typische Scheitern von Codegen-Projekten folgt einem vorhersehbaren Muster: Ein Team generiert einen API-Client aus der OpenAPI-Spezifikation, stellt fest, dass der generierte Code in bestimmten Bereichen unbrauchbar ist, und beginnt den generierten Code manuell anzupassen. Beim nächsten Generierungslauf werden diese manuellen Änderungen überschrieben. Das Team hört auf zu regenerieren und pflegt den Client von da an manuell weiter. Nach sechs Monaten ist der Client veraltet und das Team hat denselben Wartungsaufwand wie zuvor, aber zusätzlich mit einem schwer lesbaren Code-Stil als Erbe der ursprünglichen Generierung.

Der Fehler liegt nicht in Codegen als Konzept, sondern in der Annahme, dass ein Generator sofort produktionsfähigen Code ohne Konfiguration liefert. Wie jedes Werkzeug braucht openapi-generator eine initiale Investition: Generator-Auswahl, Konfigurationsdatei, Template-Overrides für projektspezifische Konventionen und eine klare Strategie, was generiert und was manuell geschrieben wird. Diese Investition zahlt sich aus: Wenn die API-Spezifikation sich ändert, wird der Client in Sekunden aktualisiert, Tippfehler in Endpunkt-URLs und Feldnamen sind unmöglich, und neue Entwickler müssen keine manuelle Dokumentation lesen, um den Client zu verstehen.

2. openapi-generator: Generator-Auswahl und Basis-Setup

Das Projekt OpenAPITools/openapi-generator unterstützt über 60 Sprachen und Frameworks als Ziel. Die Wahl des richtigen Generators für die Zielsprache ist nicht trivial: Für PHP gibt es php (Guzzle-basiert), php-nextgen (PSR-18, moderner), und bei Symfony-Projekten direkt php-symfony für Server-Stubs. Für TypeScript-Frontends gibt es typescript-fetch, typescript-axios und typescript-node. Jeder Generator hat eigene Optionen, Template-Strukturen und Qualitätsniveaus – ein sorgfältiger Vergleich der Ausgabe lohnt sich vor der Entscheidung.

Die empfohlene Installationsmethode für Teams ist das Docker-Image, das alle Java-Abhängigkeiten kapselt. Ein Shell-Wrapper-Script im Projektroot sorgt dafür, dass alle Entwickler dieselbe Generator-Version verwenden. Die Alternative npx @openapitools/openapi-generator-cli ist für Node-Projekte bequemer, aber die Generator-Version muss explizit fixiert werden, weil verschiedene Versionen unterschiedliche Template-Defaults haben und eine automatische Versionsänderung die generierten Artefakte unerwartet verändern kann.


#!/usr/bin/env bash
# bin/generate-client — wrapper for consistent generator version
set -euo pipefail

GENERATOR_VERSION="7.5.0"
SPEC_FILE="${1:-openapi.json}"
OUTPUT_DIR="${2:-generated/php-client}"
CONFIG_FILE="${3:-codegen/php-client.yaml}"

echo "Generating PHP client from ${SPEC_FILE}..."

docker run --rm \
  -v "$(pwd):/workspace" \
  -w /workspace \
  "openapitools/openapi-generator-cli:v${GENERATOR_VERSION}" generate \
    --input-spec "/workspace/${SPEC_FILE}" \
    --generator-name php-nextgen \
    --output "/workspace/${OUTPUT_DIR}" \
    --config "/workspace/${CONFIG_FILE}" \
    --skip-validate-spec

echo "Generation complete: ${OUTPUT_DIR}"

Wichtig ist das Flag --skip-validate-spec mit Bedacht: Es überspringt die Schema-Validierung und ermöglicht die Generierung aus leicht invaliden Spezifikationen. In einem reifen Projekt, in dem die Spezifikation gegen einen Linter läuft, sollte dieses Flag nicht gesetzt sein. Im Bootstrapping-Stadium einer Migration ist es nützlich, um die Generierung trotz noch offener Schema-Fehler zu ermöglichen.

3. Konfigurationsdatei: alle Hebel an einem Ort

Die Konfigurationsdatei (YAML oder JSON) ist das Herzstück einer wartbaren Codegen-Strategie. Sie legt fest, welcher Namespace verwendet wird, wie Klassen und Dateien benannt werden, welche Features aktiviert sind und welche Template-Overrides geladen werden. Alle projektspezifischen Anpassungen gehören in diese Datei – nicht in Shell-Argumente, die sich in verschiedenen Scripts verselbstständigen. Versioniert in der Repository, bildet sie die vollständige Spezifikation des Generierungsprozesses ab.

Die wichtigsten Konfigurationsoptionen für PHP-Projekte sind invokerPackage für den Namespace, packageName für den Composer-Package-Namen, modelNamePrefix und modelNameSuffix für konsistente Klassennamen sowie apiNameSuffix. Mit withInterfaces: true generiert der php-nextgen-Generator für jede API-Klasse auch ein Interface – nützlich für Dependency Injection in Symfony. Mit composerVendorName und composerProjectName wird eine vollständige composer.json für das generierte Paket erzeugt.


# codegen/php-client.yaml — generator configuration
generatorName: php-nextgen
inputSpec: openapi.json
outputDir: generated/php-client

# PHP namespace and package settings
additionalProperties:
  invokerPackage: "Mironsoft\\ApiClient"
  apiPackage: "Mironsoft\\ApiClient\\Api"
  modelPackage: "Mironsoft\\ApiClient\\Model"
  composerVendorName: "mironsoft"
  composerProjectName: "api-client"
  phpVersion: "8.4"
  withInterfaces: true
  variableNamingConvention: camelCase
  packageName: "mironsoft-api-client"

# Template overrides directory
templateDir: codegen/templates/php-nextgen

# Global properties
globalProperties:
  generateAliasAsModel: true
  skipFormModel: true

# Files to skip (use .openapi-generator-ignore for per-file control)

4. Template-Overrides: Müll durch eigene Mustache-Templates ersetzen

Template-Overrides sind der wichtigste Mechanismus, um generierten Code an Projektkonventionen anzupassen, ohne den Generator selbst zu patchen. Jeder openapi-generator-Generator verwendet Mustache-Templates, die überschrieben werden können, indem man sie in einem lokalen Verzeichnis ablegt und mit --template-dir auf dieses Verzeichnis verweist. Nur die Templates, die man ablegt, werden überschrieben – alle anderen verwenden weiterhin die Generator-Defaults.

Typische Kandidaten für Template-Overrides: Die Model-Klasse, um readonly-Properties und Constructor Property Promotion in PHP 8.4 zu nutzen statt des generierten Getter/Setter-Boilerplates. Die API-Klasse, um PSR-18-kompatible HTTP-Clients statt der eingebetteten Guzzle-Konfiguration zu verwenden. Der Dateiheader, um Lizenz-Header und PHPDoc-Blöcke zu standardisieren. Man exportiert die Original-Templates mit openapi-generator author template -g php-nextgen -o templates/, kopiert die gewünschten Dateien in das lokale Override-Verzeichnis und passt sie an.

5. PHP-Client generieren: Guzzle, PSR-18 und Namespace-Kontrolle

Für PHP-API-Clients ist die Wahl zwischen dem klassischen php-Generator (fest an Guzzle gebunden) und dem moderneren php-nextgen-Generator (PSR-18-kompatibel) eine strategische Entscheidung. PSR-18 erlaubt es, den HTTP-Client durch jeden PSR-18-kompatiblen Adapter auszutauschen – Guzzle, Symfony HttpClient, cURL. In Symfony-Projekten bedeutet das, dass der Symfony HttpClient ohne Adapter direkt als PSR-18-Client in den generierten Client injiziert werden kann. Das reduziert Abhängigkeiten und ermöglicht zentrale Konfiguration von Timeouts, Retries und Proxy-Einstellungen.

Ein häufiges Problem bei generierten PHP-Clients ist die Namensgebung: Felder, die in der API als created_at (snake_case) übertragen werden, sollten im PHP-Code als createdAt (camelCase) verfügbar sein. Der Generator muss explizit konfiguriert werden, um diese Konvention anzuwenden, und die Serialisierungsschicht muss entsprechend konfiguriert sein. Mit variableNamingConvention: camelCase in der Konfiguration und einem passenden Serializer (Symfony Serializer oder JMS Serializer) ist das ohne Template-Overrides lösbar.

6. TypeScript-Client: fetch-basiert ohne Klassen-Overhead

Für Frontend-Teams ist der typescript-fetch-Generator oft die bessere Wahl als typescript-axios, weil er keine zusätzliche Abhängigkeit einführt und mit dem nativen fetch-API arbeitet, das in allen modernen Browsern und Node.js verfügbar ist. Das Ergebnis sind einfache Funktionen statt komplexer Klassen-Hierarchien, die mit Tree-Shaking in Bundlern besser funktionieren. Mit der Option supportsES6: true und useSingleRequestParameter: true erzeugt der Generator kompaktere, modernere API-Funktionen.

Der generierte TypeScript-Client enthält vollständig typisierte Request- und Response-Interfaces, die direkt aus den OpenAPI-Schemas abgeleitet werden. Wenn die API ein neues Feld hinzufügt, aktualisiert eine Neugenerierung das Interface, und TypeScript meldet sofort alle Stellen im Frontend-Code, die das neue Feld ignorieren oder verwenden sollen. Das ist der entscheidende Vorteil gegenüber manuell geschriebenen Clients: Breaking Changes in der API werden zu Compile-Fehlern im Client-Code, bevor sie in Produktion gelangen.


# codegen/typescript-client.yaml
generatorName: typescript-fetch
inputSpec: openapi.json
outputDir: generated/typescript-client

additionalProperties:
  supportsES6: true
  useSingleRequestParameter: true
  withInterfaces: true
  npmName: "@mironsoft/api-client"
  npmVersion: "1.0.0"
  typescriptThreePlus: true
  modelPropertyNaming: camelCase
  enumPropertyNaming: UPPERCASE

templateDir: codegen/templates/typescript-fetch

# Generated file: generated/typescript-client/src/apis/ProductsApi.ts
# Usage in frontend:
#
# import { ProductsApi, Configuration } from '@mironsoft/api-client';
#
# const api = new ProductsApi(new Configuration({
#   basePath: process.env.NEXT_PUBLIC_API_URL,
#   accessToken: () => getAuthToken(),
# }));
#
# const product = await api.createProduct({
#   productCreateRequest: { name: 'Headphones', price: 4999, categoryId: uuid }
# });

7. Server-Stubs: Interfaces generieren, Implementierung selbst schreiben

Server-Stubs sind die unterschätzte Nutzungsform von Codegen. Statt den gesamten Server zu generieren, generiert man nur die Interfaces und abstrakten Klassen, die den API-Vertrag definieren, und implementiert die Geschäftslogik selbst. In Symfony eignet sich dafür der php-symfony-Generator, der Controller-Interfaces und Response-DTOs erzeugt. Jede Methode des Interfaces entspricht einem API-Endpunkt, mit korrekt typisierten Parametern und Return-Typen. Symfony-Controller implementieren diese Interfaces – und wenn sich die API-Spezifikation ändert und das Interface eine neue Methode bekommt, erzwingt PHP einen Compile-Fehler, bis alle Controller-Implementierungen aktualisiert sind.

Dieser Ansatz ist besonders wertvoll bei versionierten APIs. Wenn eine neue API-Version ein anderes Schema einführt, generiert man die neuen Interfaces und implementiert sie separat, während die alten Controller weiterhin die v1-Interfaces implementieren. Die Spezifikation ist die einzige Source of Truth für den Vertrag; Code, der nicht dem Vertrag entspricht, lässt sich nicht kompilieren. Das ist strikter Vertragsbindung ohne zusätzliche Laufzeit-Überprüfung.

8. Generierte Artefakte versionieren oder nicht?

Die Frage, ob generierte Artefakte in der Versionskontrolle liegen sollen, ist eine der häufigsten Diskussionen bei Codegen-Projekten. Die Argumente gegen Versionierung: Generierter Code ist abgeleitet und gehört nicht in die Versionskontrolle – nur die Quelle (OpenAPI-Spezifikation) wird versioniert, der Client wird bei Bedarf generiert. Die Argumente für Versionierung: Entwickler können den Client sofort nutzen, ohne den Generator zu installieren; diffs im Review zeigen exakt, welche API-Änderungen welche Code-Änderungen verursachen; der Client ist auch ohne Generierungs-Toolchain verfügbar.

Die pragmatische Empfehlung für die meisten Teams: Den generierten Client als eigenständiges Paket versionieren und über den internen Package-Registry (Composer Private Packagist, npm Registry) veröffentlichen. CI regeneriert den Client bei jeder Änderung der Spezifikation, erstellt eine neue Paketversion und publiziert sie. Konsumenten des Clients aktualisieren die Paketversion in ihrer composer.json oder package.json. Das entkoppelt Client-Konsumenten von der Generierungs-Toolchain und macht Updates explizit und kontrollierbar.

9. Generator-Varianten im direkten Vergleich

Die Auswahl des richtigen Generators für jede Kombination aus Zielsprache und Anforderungen ist entscheidend für die Qualität des generierten Codes. Die folgende Tabelle gibt einen Überblick über die wichtigsten Optionen.

Generator Zielsprache HTTP-Client Empfehlung
php-nextgen PHP 8.x PSR-18 (austauschbar) Empfohlen für PHP-Clients
php-symfony PHP / Symfony Controller-Interfaces Empfohlen für Server-Stubs
typescript-fetch TypeScript native fetch Empfohlen für Frontends
typescript-axios TypeScript axios Wenn axios bereits vorhanden
go Go net/http Für Go-Microservices

Unabhängig vom gewählten Generator gilt: Ein Template-Override für den Dateiheader, der einen @generated-Hinweis und ein @do-not-edit-Kommentar enthält, verhindert, dass Entwickler versehentlich generierte Dateien manuell bearbeiten. Ein Pre-Commit-Hook, der prüft, ob generierte Dateien manuell geändert wurden, ist eine zusätzliche Absicherung.

Mironsoft

REST API Design, Code-Generierung und CI/CD-Integration

Sauberen generierten API-Client ohne manuelle Patches?

Wir richten openapi-generator mit projektspezifischen Template-Overrides, Konfigurationsdateien und CI-Pipelines für PHP- und TypeScript-Projekte ein – so dass jede Spezifikationsänderung automatisch zu sauberem, wartbarem Client-Code führt.

Generator-Setup

Konfigurationsdatei, Template-Overrides und Wrapper-Script für konsistente Versionen

CI-Integration

Automatische Regenerierung bei Spezifikationsänderungen, Paketpublizierung in Private Registry

Server-Stubs

Symfony-Controller-Interfaces aus OpenAPI-Spec generieren und in bestehende Architektur einbinden

10. Zusammenfassung

Sauberer generierter Code erfordert initiale Konfigurationsarbeit, zahlt sich danach aber mit jedem API-Update aus. Die entscheidenden Komponenten: Eine versionierte Konfigurationsdatei mit allen Namespace-, Namensgebungs- und Feature-Optionen. Template-Overrides für projektspezifische Konventionen wie PHP 8.4-Syntax oder TypeScript-Modularisierung. Ein Wrapper-Script, das die Generator-Version fixiert, sodass alle Entwickler und CI identische Ausgaben erzeugen. Eine klare Strategie für generierte Artefakte – Paketpublizierung über Private Registry entkoppelt Konsumenten von der Generierungs-Toolchain.

Der wichtigste Grundsatz: Generierte Dateien niemals manuell bearbeiten. Wenn der generierte Code an einer Stelle unbefriedigend ist, ist die Lösung ein Template-Override oder eine Konfigurationsänderung – keine manuelle Korrektur, die beim nächsten Regenerierungslauf verloren geht. Wer dieses Prinzip durchhält, hat nach der initialen Einrichtungsarbeit einen Client, der sich mit einem einzigen Befehl immer auf dem Stand der Spezifikation hält.

OpenAPI Codegen ohne Müll — Das Wichtigste auf einen Blick

Konfigurationsdatei

Alle Optionen in einer versionierten YAML-Datei – Namespace, Namensgebung, Features. Keine lose Shell-Argumente.

Template-Overrides

Mustache-Templates für projektspezifische Konventionen überschreiben – PHP 8.4 Syntax, TypeScript-Module, Dateiheader.

Version fixieren

Docker-Image mit fixer Generator-Version im Wrapper-Script – identische Ausgabe auf allen Entwicklermaschinen und in CI.

Kein manuelles Patchen

Generierte Dateien nie manuell bearbeiten. Verbesserungen immer über Template-Overrides oder Konfiguration lösen.

11. FAQ: OpenAPI Codegen für Clients und Server-Stubs

1Warum scheitern Codegen-Projekte so oft?
Weil Teams den generierten Code manuell patchen statt Template-Overrides zu nutzen. Beim nächsten Generierungslauf gehen die Änderungen verloren – und das Team hört auf zu regenerieren.
2php vs. php-nextgen?
php: feste Guzzle-Bindung. php-nextgen: PSR-18-kompatibel, HTTP-Client austauschbar. In Symfony direkt den Symfony HttpClient als PSR-18-Adapter verwenden.
3Manuelle Bearbeitungen generierter Dateien verhindern?
@generated-Kommentar im Header-Template, Pre-Commit-Hook der Änderungen an generierten Dateien ablehnt, .openapi-generator-ignore für selektive Generierung.
4Generierte Artefakte in Git versionieren?
Besser als eigenständiges Paket über Private Registry veröffentlichen. CI regeneriert und publiziert neue Versionen. Konsumenten aktualisieren explizit.
5Was sind Template-Overrides?
Mustache-Templates aus dem Generator exportieren, die zu ändernden Dateien kopieren und anpassen, per --template-dir einbinden. Nur überschriebene Templates weichen vom Default ab.
6typescript-fetch statt typescript-axios?
Keine zusätzliche Abhängigkeit, tree-shakeable, funktioniert in Browsern und Node.js. axios sinnvoll, wenn es bereits im Projekt vorhanden ist.
7Was sind Server-Stubs?
Generierte Interfaces und abstrakte Klassen für den API-Vertrag. Controller implementieren diese Interfaces – Spec-Änderungen erzwingen Compile-Fehler bis alle Implementierungen aktualisiert sind.
8Generator-Version für alle Entwickler fixieren?
Wrapper-Script mit Docker-Image und fixer Tag-Version: openapitools/openapi-generator-cli:v7.5.0 – identische Ausgabe auf allen Maschinen und in CI.
9Nur bestimmte Teile der Spec generieren?
.openapi-generator-ignore-Datei und globalProperties wie apis=false oder models=false. Nur Model-Klassen oder nur API-Klassen generieren ist problemlos möglich.
10Breaking Changes in der API-Spec handhaben?
Breaking Changes in der Spec erzeugen Breaking Changes im generierten Code – TypeScript-Compile-Fehler, PHP-Typ-Fehler. Das macht Breaking Changes explizit vor der Produktion.