Von der Schnittstellenbeschreibung bis zum geprüften Objekt
Typen sind eine Behauptung über fremde Daten. Erst die Prüfung an der Systemgrenze und ein Abgleich in der Pipeline machen daraus eine Zusage, auf die der Rest bauen darf.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Fehler zeigt sich in der Oberfläche, entstanden ist er an der Grenze
Der typische Ablauf: Jemand schreibt ein Interface passend zur Beispielantwort, holt die Daten mit fetch, hängt ein as an die Stelle, an der TypeScript unknown liefert, und ab da behandelt die ganze Anwendung die Daten als gesichert. Das funktioniert genau so lange, bis ein Feld optional wird, ein Datum als Zeichenkette statt als Zahl kommt oder eine Liste bei leerem Ergebnis null statt eines leeren Arrays liefert.
Auffallen wird das an einer ganz anderen Stelle, meistens tief in einer Komponente, die eine Eigenschaft eines Objekts liest, das es nicht gibt. Die Fehlermeldung nennt dann diese Komponente, nicht die Schnittstelle. Bis jemand den Weg zurückverfolgt hat, sind mehrere Personen beteiligt, und die Vermutung steht im Raum, das Frontend habe einen Fehler.
Der zweite, teurere Fall ist die stille Abweichung. Das Backend benennt ein Feld um und liefert das alte für eine Übergangszeit weiter mit, oder ein Wertebereich bekommt einen zusätzlichen Status. Nichts stürzt ab, die Oberfläche zeigt nur an einer Stelle nichts an. Ohne Prüfung an der Grenze merkt das niemand, bis eine Fachabteilung fragt, warum eine Zahl nicht mehr stimmt.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Weg von der Beschreibung bis zur geprüften Antwort
Die Reihenfolge ist wichtiger als die Werkzeugwahl. Wer mit Schritt drei anfängt, prüft am Ende Daten gegen Typen, die jemand von Hand abgeschrieben hat, und hat damit nur das eigene Missverständnis bestätigt.
- 1
Die Beschreibung beschaffen
Frag nach der OpenAPI-Datei oder dem GraphQL-Schema und danach, wo sie erzeugt wird. Kommt sie aus dem Quellcode des Servers, ist sie aktuell. Liegt sie als gepflegtes Dokument daneben, ist sie eine Absichtserklärung, und du musst mit Abweichungen rechnen.
Geschafft, wenn: Es gibt eine Datei mit einer festen Adresse, die niemand von Hand nachbessert.
- 2
Typen daraus erzeugen
Für REST erzeugt openapi-typescript aus der Beschreibung eine Typdatei, die Pfade, Parameter und Antworten je Statuscode abbildet. Für GraphQL übernimmt das der GraphQL Code Generator, der zusätzlich die konkreten Abfragen typisiert. Der Aufruf gehört als Skript ins Repository, damit ihn nicht jeder anders ausführt.
Geschafft, wenn: Eine erzeugte Datei liegt im Projekt, und niemand bearbeitet sie von Hand.
- 3
Den Aufruf typisieren
Setz einen dünnen Client auf die erzeugten Typen, bei openapi-typescript zum Beispiel openapi-fetch, der Pfad, Methode und Antwort zusammenführt. Damit meldet der Compiler einen falschen Pfad oder einen fehlenden Parameter beim Schreiben und nicht der Server zur Laufzeit.
Geschafft, wenn: Ein Tippfehler im Pfad wird zum Fehler im Editor.
- 4
Die Antwort an der Grenze prüfen
Beschreib die Antwort zusätzlich als Schema, etwa mit Zod, und prüf sie mit safeParse, bevor du sie weitergibst. Der Rückgabewert unterscheidet Erfolg und Fehler, statt eine Ausnahme zu werfen, und der abgeleitete Typ kommt aus demselben Schema, sodass Prüfung und Typ nicht auseinanderlaufen können.
Geschafft, wenn: Eine unerwartete Antwort erzeugt eine klare Meldung mit Feldnamen statt eines Folgefehlers.
- 5
Die Fehlerfälle sauber trennen
Gib aus der Anbindung ein Ergebnis zurück, das die Fälle Erfolg, Statusfehler, Netzwerkfehler und Formatabweichung unterscheidet. Eine Vereinigung mit einem gemeinsamen Feld reicht dafür aus, und die Oberfläche kann darauf verzweigen, ohne den Fehlertext zu zerlegen.
Geschafft, wenn: Die Komponente zeigt bei einem 404 etwas anderes als bei einem Verbindungsabbruch.
- 6
Die Prüfung in die Pipeline hängen
Lass die Typen im Bauschritt neu erzeugen und vergleich sie mit den eingecheckten. Weicht etwas ab, bricht der Lauf mit einem Hinweis auf das geänderte Feld. Das ist die Stelle, an der eine stille Änderung am Backend laut wird.
Geschafft, wenn: Eine umbenannte Eigenschaft fällt im Bauschritt auf, nicht in der Fehlermeldung eines Kunden.
- 7
Mocks aus derselben Quelle ziehen
Leite Testantworten aus der Beschreibung oder aus dem Schema ab, statt sie als Beispiel-JSON zu pflegen. Ein Werkzeug wie Mock Service Worker fängt die Anfragen im Test ab und liefert diese Antworten aus, sodass Tests und Anbindung dieselbe Grundlage teilen.
Geschafft, wenn: Ein geändertes Feld lässt Tests fehlschlagen, statt sie unverändert grün zu lassen.
Fünf Stationen zwischen Server und Komponente
- 01 Beschreibung: OpenAPI oder GraphQL-Schema als gemeinsame Grundlage.
- 02 Erzeugung: Typen und Client entstehen aus dieser Datei, nicht von Hand.
- 03 Grenze: Die Antwort wird einmal geprüft, danach gilt sie als gesichert.
- 04 Fehlerpfad: Netzwerk, Status und Formatabweichung bleiben unterscheidbar.
- 05 Pipeline: Neu erzeugte Typen werden verglichen, ein Unterschied bricht den Build.
Was du nach diesem Vorgehen sicher sagen kannst
Ziel ist nicht die perfekte Typdefinition, sondern eine Stelle im Code, an der aus fremden Daten geprüfte Daten werden, und ein Signal, das anschlägt, bevor jemand anderes es merkt. Beides zusammen kostet einen Tag Einrichtung und spart die Suche nach Fehlern, die sich als Anzeigeproblem tarnen.
Die Quelle des Vertrags benennen
Entweder es gibt eine OpenAPI-Beschreibung oder ein GraphQL-Schema, dann ist die Frage geklärt. Gibt es keine, wird das zur ersten Aufgabe, denn ohne eine gemeinsame Beschreibung bleibt jede Typisierung eine private Vermutung über fremden Code.
Typen erzeugen statt abtippen
Aus einer OpenAPI-Datei erzeugt openapi-typescript einen Satz Typen, aus einem GraphQL-Schema erledigt das der GraphQL Code Generator. Der Gewinn ist nicht die gesparte Tipparbeit, sondern dass die Typen bei jeder Änderung neu entstehen und niemand vergisst, sie nachzuziehen.
Eine einzige Prüfstelle einrichten
Die Laufzeitprüfung gehört in die Funktion, die die Antwort entgegennimmt, nicht in die Komponenten. Danach gilt im Rest der Anwendung der geprüfte Typ, und es gibt genau einen Ort, an dem eine unerwartete Antwort sichtbar wird.
Fehler als Ergebnis behandeln
Netzwerkfehler, Statuscode jenseits von 200 und eine Antwort, die nicht zur Beschreibung passt, sind drei verschiedene Fälle. Wenn die Anbindung sie als unterscheidbares Ergebnis zurückgibt statt als Ausnahme, kann die Oberfläche für jeden Fall etwas Sinnvolles anzeigen.
Abweichungen in der Pipeline sichtbar machen
Erzeugte Typen gehören ins Repository, und ein Schritt in der Pipeline erzeugt sie neu und vergleicht. Bleibt ein Unterschied übrig, hat sich die Schnittstelle geändert, und das merkt ihr beim Bauen statt im Betrieb.
Testdaten aus derselben Beschreibung ziehen
Wenn Mocks von Hand entstehen, veralten sie mit der Schnittstelle und bestätigen danach nur noch sich selbst. Aus der Beschreibung abgeleitete Antworten halten Tests und Wirklichkeit zusammen.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Ein Typ ist eine Behauptung, keine Prüfung
TypeScript prüft, ob dein Code in sich stimmig ist. Was zur Laufzeit über das Netz kommt, sieht der Compiler nie. Deshalb liefert eine Antwort in TypeScript zunächst any oder unknown, und jedes as an dieser Stelle ist eine Behauptung, die niemand kontrolliert. Genau dort entsteht der Unterschied zwischen einem Programm, das bei falschen Daten eine verständliche Meldung zeigt, und einem, das an beliebiger Stelle stehen bleibt.
Die Prüfung an der Grenze kostet Rechenzeit, und das ist bei sehr großen Listen ein reales Argument. Der übliche Kompromiss: Antworten mit begrenzter Größe vollständig prüfen, bei sehr großen Sammlungen die Hülle prüfen und die Einträge stichprobenartig. Was du nicht tun solltest, ist die Prüfung wegzulassen und stattdessen in jeder Komponente defensiv auf Existenz zu testen, denn dann steht die Fehlerbehandlung an fünfzig Stellen und die Fehlerursache an keiner.
Erzeugte Typen gegen handgeschriebene
Handgeschriebene Typen sind vertretbar, wenn du zwei Endpunkte anbindest, die sich nicht ändern, und niemand sonst an ihnen arbeitet. Sobald mehrere Teams beteiligt sind, kippt die Rechnung, weil eine Änderung am Backend keine Spur im Frontend hinterlässt. Erzeugte Typen drehen das um: Nach jeder Erzeugung zeigt der Compiler an, wo die neue Beschreibung nicht mehr zum vorhandenen Code passt.
Die Grenzen liegen in der Qualität der Beschreibung. Wenn eine OpenAPI-Datei jedes Feld als optional oder als beliebiges Objekt beschreibt, sind auch die erzeugten Typen nutzlos. Achte besonders auf nullable-Angaben, denn ein Feld, das als Zeichenkette beschrieben ist und in Wirklichkeit null sein kann, produziert die unangenehmsten Fehler. Solche Abweichungen findet man nicht durch Lesen, sondern durch die Laufzeitprüfung, die dann meldet, dass Beschreibung und Wirklichkeit auseinandergehen.
Der Vollständigkeit halber: Es gibt Werkzeuge, die aus der Beschreibung gleich den ganzen Client samt Prüfschemata erzeugen. Das nimmt Arbeit ab und bindet dich an deren Vorstellung von Fehlerbehandlung, Zwischenspeicher und Abbruch. Für kleine Anbindungen lohnt der eigene dünne Client, für große Schnittstellenlandschaften spart die vollständige Erzeugung mehr, als sie an Freiheit kostet.
Wo die Laufzeitprüfung hingehört und wo nicht
Die Prüfung gehört an die Systemgrenze, also in die Funktion, die die Antwort entgegennimmt. Danach ist der Typ garantiert und der Rest der Anwendung arbeitet ohne zusätzliche Absicherung. Mit Zod schreibst du das Schema einmal und leitest den Typ daraus ab, sodass es keine zweite Quelle geben kann, die sich unbemerkt davon entfernt. safeParse ist dabei die richtige Variante, weil das Ergebnis den Fehlerfall mitführt, statt eine Ausnahme zu werfen, die irgendwo weiter oben gefangen wird.
Zwei Dinge lohnen sich an dieser Stelle zusätzlich. Erstens die Umwandlung: Ein ISO-Datum kommt als Zeichenkette an, und wenn es die Prüfung ohnehin anfasst, kann sie daraus direkt ein Date machen. Zweitens die Meldung: Der Fehlerpfad aus der Prüfung nennt das betroffene Feld, und dieser Pfad gehört ins Protokoll. Damit steht in der Fehlermeldung, welches Feld nicht gepasst hat, und nicht nur, dass die Antwort unerwartet war.
Nicht sinnvoll ist die Prüfung in Komponenten, in jedem Reducer oder rund um Daten, die deine eigene Anwendung erzeugt hat. Innerhalb des Systems gilt der Typ. Wer überall prüft, verlangsamt die Anwendung und verwässert die Aussage, denn dann sagt eine fehlgeschlagene Prüfung nicht mehr, dass eine fremde Quelle etwas anderes geliefert hat.
Was passiert, wenn sich die Schnittstelle ändert
Änderungen sind der Normalfall, und die Frage ist nur, wer sie zuerst bemerkt. Additive Änderungen, also neue Felder und neue optionale Parameter, sind ungefährlich. Gefährlich sind Umbenennungen, verschärfte Pflichtfelder, geänderte Typen und neue Werte in einer Aufzählung, denn genau diese Fälle sehen in der Antwort harmlos aus.
Der Abgleich in der Pipeline ist die billigste Absicherung: Typen neu erzeugen, mit den eingecheckten vergleichen, bei Unterschied abbrechen. Das kostet einen Schritt und liefert im Fehlerfall eine Zeile, die genau sagt, welches Feld sich geändert hat. Für neue Werte in einer Aufzählung hilft zusätzlich, im Code über alle Fälle zu verzweigen und den nicht behandelten Fall vom Compiler bemängeln zu lassen, statt einen Standardzweig zu bauen, der alles Unbekannte still schluckt.
Wenn Backend und Frontend getrennt ausgerollt werden, gehört noch eine Regel dazu: Die Beschreibung ändert sich zuerst, dann das Frontend, dann fällt das alte Feld weg. Wer diese Reihenfolge einhält, braucht keine Absprache über Zeitpunkte, und wer sie nicht einhält, braucht sie bei jeder Änderung neu.
GraphQL verschiebt die Frage, es beantwortet sie nicht
Bei GraphQL ist das Schema Teil des Servers, es kann also nicht veralten. Die erzeugten Typen beziehen sich nicht nur auf Objekte, sondern auf deine konkreten Abfragen, sodass jedes Feld, das du nicht abgefragt hast, im Typ auch nicht auftaucht. Das ist der eigentliche Vorteil gegenüber REST: Der Typ beschreibt genau das, was du angefordert hast.
Bleiben zwei Punkte, die Teams regelmäßig überraschen. Erstens die Nullbarkeit: In GraphQL ist alles voreingestellt nullbar, und ein Fehler in einem Teilbaum liefert Daten und Fehler gleichzeitig. Deine Anbindung muss also mit einer Antwort umgehen, die teilweise gefüllt ist. Zweitens die Frage, wer Abfragen kontrolliert: Ein Client kann sehr teure Abfragen stellen, deshalb arbeiten viele Teams mit fest hinterlegten Abfragen, was den Vorteil der freien Zusammenstellung wieder einschränkt.
Für die Typisierung heißt das: Der Weg ist derselbe, nur die Quelle ist eine andere. Schema holen, Typen für die eigenen Abfragen erzeugen, Antwort an der Grenze auf Vollständigkeit prüfen, Fehlerfeld auswerten. Wer das bei REST eingeübt hat, überträgt es in einem Nachmittag.
Dazu passende Kurse
Wer das an eigenen Endpunkten durchspielen möchte, findet bei cmt die passenden Termine, um Serverantworten zuverlässig typisieren .
Weil diese Frage nicht am Editor hängt, sondern am Zuschnitt der Anwendung, lohnt es sich, die Grenze zwischen Frontend und Backend sauber schneiden .
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
Häufige Fragen
Reicht es nicht, die Antwort einfach mit as auf den erwarteten Typ zu zwingen?
Kostet eine Laufzeitprüfung nicht spürbar Leistung?
Wir haben keine OpenAPI-Beschreibung. Womit fangen wir an?
Wohin gehören Daten aus der Schnittstelle im Frontend?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Schnittstellen anbinden, ohne auf gutes Wetter zu hoffen
Bei cmt baust du eine solche Anbindung einmal vollständig auf, von der Beschreibung über die erzeugten Typen bis zur Prüfung, die im Fehlerfall etwas Verständliches sagt.