09. Okt. 2025·8 Min. Lesezeit

Frontend‑Backend‑Vertragsabweichungen, die das Speichern von Formularen verhindern

Frontend‑Backend‑Vertragsabweichungen lassen Formulare erfolgreich aussehen, obwohl nichts gespeichert wird. Stimmen Sie DTOs, Validierungsfehler, Statuscodes und Paginierungsformate ab.

Frontend‑Backend‑Vertragsabweichungen, die das Speichern von Formularen verhindern

Was es bedeutet, wenn ein Formular abschickt, aber nichts gespeichert wird

Ein Frontend‑Backend‑Vertrag ist die geteilte Vereinbarung zwischen deinem Formular und deiner API: welche Felder gesendet werden, wie sie heißen, welchen Typ sie haben und was die API bei Erfolg oder Fehler zurückgibt.

Wenn ein Formular "abschickt", aber nichts gespeichert wird, bedeutet das meist: Das Frontend hat seine Arbeit gemacht (es hat eine Anfrage gesendet), aber das Backend hat die Daten nicht persistiert. Das Schwierige ist, dass der Nutzer oft keinen klaren Fehler sieht, weil die Antwort wie OK aussieht, die UI die Antwort ignoriert oder die API ein unerwartetes Format zurückgibt.

Ein typisches Beispiel: Das Formular postet { email, password }, aber die API erwartet { userEmail, passwordHash }. Die Anfrage kommt beim Server an, die Validierung schlägt fehl, und die API gibt ein generisches 200 mit { ok: false } (oder einen leeren Body) zurück. Das Frontend interpretiert jedes 200 als Erfolg, zeigt einen Toast, und der Nutzer geht weiter, obwohl die Datenbank nie geändert wurde.

Das passiert oft bei schnell erstellten oder KI-generierten Prototypen. Tools können UI und API rasch erzeugen, raten aber oft Feldnamen falsch, vergessen serverseitige Validierungsregeln oder erfinden Antwortformate. Wenn du später einen Mock-Endpunkt gegen einen echten austauschst, verschiebt sich der "Vertrag" leicht, und Speichervorgänge schlagen still fehl.

Das zu beheben ist selten nur eine Änderung in einer Datei. Typischerweise musst du mehrere Teile über den Stack hinweg angleichen:

  • Request-DTOs (Feldnamen, Typen, required vs optional)
  • Validierungsfehler (ein konsistentes Format, das die UI neben Feldern anzeigen kann)
  • Statuscodes (damit Erfolg und Fehler eindeutig sind)
  • Erfolgsantworten (gib zurück, was die UI braucht, um den Save zu bestätigen)
  • List-Antworten (ein Paginierungsformat, das sich nicht von Endpoint zu Endpoint ändert)

Wenn du eine KI-generierte App geerbt hast und Saves unzuverlässig sind, ist das genau die Art von Problem, die Teams wie FixMyMess sehen: der Code "läuft", aber der Vertrag ist inkonsistent. Der restliche Leitfaden konzentriert sich darauf, den Vertrag explizit, vorhersehbar und schwer versehentlich änderbar zu machen.

Häufige Symptome und worauf sie meist hinweisen

Ein Formular kann so aussehen, als wäre es erfolgreich gewesen, während sich in Wirklichkeit nichts geändert hat. Der häufigste Hinweis ist ein Erfolgstoast (oder ein grünes Häkchen), doch nach einem Refresh sind die alten Daten noch da. Das bedeutet meistens, dass die UI den Erfolg anhand des falschen Signals entschieden hat, nicht danach, was der Server tatsächlich getan hat.

Auf der Backend-Seite achte auf Antworten, die "erfolgreich" wirken, es aber nicht sind. Ein klassisches Vertragsproblem ist ein 200 OK, das einen Fehler im JSON-Body enthält (z. B. { "ok": false, "message": "invalid" }). Ein weiteres Beispiel ist 204 No Content, obwohl das Frontend den gespeicherten Datensatz zurückerwartet und eine id oder aktualisierte Felder braucht.

Auf Entwicklerseite sind die Hinweise oft klein und leicht zu übersehen: In der Konsole steht undefined für ein Feld, von dem du sicher bist, dass du es ausgefüllt hast, oder der Network-Tab zeigt eine Antwortform, die du nicht vorgesehen hast (wie data vs result, oder ein Array statt eines Objekts). Das sind Frontend‑Backend‑Vertragsabweichungen, deutlich sichtbar.

Gängige Symptome und ihre wahrscheinlichen Ursachen:

  • Erfolgsmeldung erscheint, aber nach Refresh keine Änderung: Die Anfrage hat den Save-Endpoint nie erreicht, oder sie kam mit fehlenden/umbenannten Feldern an.
  • Save funktioniert nur für manche Nutzer: Backend-Validierung weicht von Frontend-Regeln ab oder erforderliche Felder hängen von der Nutzerrolle ab.
  • Backend liefert 200, aber UI verhält sich seltsam: Der Fehler ist im JSON kodiert, nicht über den Statuscode.
  • UI zeigt „Saved“, aber die Liste zeigt weiterhin das alte Objekt: Cache wurde nicht invalidiert oder die Antwort enthält den aktualisierten Datensatz nicht.
  • Pagination wirkt kaputt (fehlende Einträge, Wiederholungen): Frontend erwartet page/total, Backend liefert nextCursor/items (oder umgekehrt).

Kurze Regel: vertraue dem Network-Tab mehr als der UI. Wenn Payload und Response nicht dem entsprechen, was dein Code annimmt, kann das Formular "abschicken" ohne dass etwas gespeichert wird.

Das ist ein typisches Muster, das wir bei FixMyMess sehen, wenn ein KI-generierter Prototyp Button und Toast verdrahtet, aber nie bestätigt, dass der Server wirklich persistiert hat.

Der Vertrag, auf den ihr euch einigen müsst: Shapes, Namen und Typen

Wenn ein Save in der UI "funktioniert", aber in der Datenbank nichts passiert, ist es oft kein Fehler an einer Stelle. Es ist eine Uneinigkeit zwischen Client und API darüber, wie eine gültige Anfrage und Antwort aussehen. Viele Frontend‑Backend‑Vertragsabweichungen sind langweilige Details — aber sie brechen die App still.

Beginnt damit, das minimale Vertragswerk für einen einzelnen Save aufzuschreiben. Wenn irgendetwas vage ist, füllt der jeweilige Teil des Stacks die Lücken unterschiedlich aus.

  • Endpoint + Methode (z. B. POST /users vs PUT /users/:id)
  • Erforderliche Header (insbesondere Content-Type und Auth)
  • Request-Body-Shape (Feldnamen, Verschachtelung, optional vs required)
  • Response-Shape (was der Client lesen soll, um die UI zu aktualisieren)
  • Error-Shape (wie Validierungsprobleme zurückgegeben werden)

Namensgebung ist der erste Ort, an dem Verträge auseinanderdriften. Wenn das Frontend firstName sendet, das Backend aber first_name erwartet, kann es passieren, dass das Backend das unbekannte Feld ignoriert oder einen Default speichert.

Typen sind der zweite Punkt. Ein häufiges Problem: Die UI sendet age: "32" als String, das Backend erwartet eine Zahl. Manche Frameworks konvertieren, manche lehnen ab, manche verwandeln fehlerhafte Werte in null. Wenn null erlaubt ist, speicherst du plötzlich einen leeren Wert ohne es zu bemerken.

Zusätzliche oder fehlende Felder können ebenfalls still verschwinden. Z. B. enthält das Formular marketingOptIn, aber das DTO auf dem Server kennt das Feld nicht. Je nach Stack wird dieses Feld beim Deserialisieren entfernt, ohne Fehler. Umgekehrt verlangt das Backend companyId, das Frontend sendet es nicht, und der Server erstellt einen Datensatz, der mit nichts verknüpft ist.

Eine praktische Methode, das früh zu erkennen: Nimm eine echte Anfrage aus den DevTools deines Browsers, vergleiche sie Zeile für Zeile mit dem Server-DTO und den Validierungsregeln und einigt euch auf genaue Namen und Typen, bevor ihr Logik ändert. Das ist die Art von Abgleich, die FixMyMess bei Audits von KI-generierten Prototypen schnell findet.

Schritt-für-Schritt: DTOs vom Formularfeld bis zur Datenbank angleichen

Wenn ein Formular "abschickt" aber nichts speichert, ist die übliche Ursache einfach: Das Frontend sendet eine Form, das Backend erwartet eine andere. Die Lösung beginnt damit, zu entscheiden, wer die Wahrheit definiert, und dann jede Station vom Formular bis zur Speicherung zu prüfen.

1) Wähle eine einzige Quelle der Wahrheit

Wählt einen Ort, der Namen und Typen definiert. Für die meisten Teams sind Backend-Request/Response-DTOs die sicherste Quelle, weil sie neben Validierung und Persistenz liegen. Wenn ihr ein geteiltes Schema verwendet, behandelt es wie einen Vertrag und versieht Änderungen mit Versionen.

2) Schreibe die DTOs mit echten Beispielen auf

Verlass dich nicht auf "ist doch klar". Schreibe ein konkretes Beispiel für Create und eines für Update. Updates scheitern oft, weil sie eine id benötigen, partielle Felder erlauben oder andere Namen verwenden.

// Create
{ "email": "[email protected]", "displayName": "Sam", "marketingOptIn": true }

// Update
{ "id": "usr_123", "displayName": "Sam Lee", "marketingOptIn": false }

Schreibe dann das Response-DTO auf, das die UI tatsächlich braucht. Wenn die UI user.id erwartet, die API aber userId zurückgibt, können Saves "funktionieren", obwohl die UI den aktualisierten Zustand nicht rendern kann.

3) Verfolge den Pfad vom Formular bis zur Datenbank

Gehe die gesamte Kette einmal durch, Ende-zu-Ende:

  • Formularfeldnamen und -typen (Strings, Zahlen, Booleans)
  • Payload über das Netzwerk (inkl. Header wie Content-Type)
  • Backend-DTO-Parsen und Validierung (required-Felder, Defaults)
  • Mapping zu DB-Spalten (Namen und Typkonvertierungen)
  • Response-Body, den die UI liest, um den Bildschirm zu aktualisieren

4) Verifiziere mit der exakten Payload, die das UI sendet

Kopiere die echte Anfrage aus dem Network-Tab und spiele sie nach. Das fängt Probleme wie "true" (String) vs true (Boolean), fehlende Felder oder unerwartete Verschachtelung auf.

5) Ändere eine Seite, teste dann ein bekanntes gutes Beispiel

Korrigiere entweder das Frontend-Mapping oder das Backend-DTO, nicht beides gleichzeitig. Behalte eine "goldene" Payload und erwartete Response, um zu bestätigen, dass du die Abweichung nicht nur verschoben hast.

Wenn du KI-generierten Code geerbt hast, sind diese Abweichungen üblich, weil generierte UIs und APIs oft getrennt weiterentwickelt werden. Plattformen wie FixMyMess starten deshalb meist mit einem Audit der Verträge und Mappings, bevor Geschäftslogik angefasst wird — dort verstecken sich stille Save-Fehler.

Mach Validierungsfehler konsistent und leicht darstellbar

Fix Validation Users Can See
Standardize validation errors so users see what to fix instead of a fake success.

Wenn Frontend und Backend uneins sind, wie Fehler aussehen, bekommen Nutzer das schlimmste Erlebnis: Das Formular "abschickt", aber niemand sagt ihnen, was zu beheben ist. Eine einfache, vorhersehbare Fehlerstruktur ist ein leichter, aber wirkungsvoller Gewinn gegen Frontend‑Backend‑Vertragsabweichungen.

Ein praktisches Muster ist, bei Validierungsfehlern immer dieselbe Struktur zurückzugeben:

{
  "error": {
    "type": "validation_error",
    "fields": [
      { "field": "email", "code": "invalid_format", "message": "Enter a valid email." },
      { "field": "password", "code": "too_short", "message": "Password must be at least 12 characters." }
    ],
    "non_field": [
      { "code": "state_conflict", "message": "This invite has already been used." }
    ]
  }
}

Behalte für jedes Problem drei Teile: den Feldnamen (passend zu eurem DTO), einen stabilen Code (damit die UI darauf reagieren kann) und eine menschenlesbare Nachricht (zur Anzeige). Wenn du nur Nachrichten zurückgibst, muss die UI raten und bricht, wenn sich Texte ändern.

Nicht-Feld-Fehler sind genauso wichtig. Berechtigungen, State-Konflikte und Rate-Limits hängen nicht an einem einzelnen Input, daher gehören sie in einen separaten Bereich wie non_field (oder global). Die UI kann diese nahe dem Submit-Button oder als kleinen Banner anzeigen.

Im Frontend sollte das Mapping langweilig und konsistent sein:

  • Vor dem Submit vorherige Fehler löschen.
  • Für jedes fields[]-Element die message dem passenden Input zuordnen.
  • Ist ein Feld unbekannt, als globalen Fehler behandeln (häufig ein Zeichen für DTO-Drift).
  • non_field[]-Meldungen an einer sichtbaren Stelle zeigen.

Und verstecke Validierung nicht in "erfolgreichen" Antworten. Wenn der Save fehlgeschlagen ist, gib einen Error-Response mit Body zurück. Fehler in einem 200 zu verschachteln ist die Ursache für stille Fehler, besonders in KI-generierten Apps, die wir bei FixMyMess sehen.

Statuscodes und ehrliche Erfolgsantworten

Viele Frontend‑Backend‑Vertragsabweichungen beginnen mit einer einfachen Unwahrheit: Der Server gibt einen Erfolgscode zurück, aber die UI kann nicht erkennen, ob der Save tatsächlich passiert ist. Wenn das Frontend jedes 200 als "gespeichert" ansieht, bekommt man den klassischen Fall: Toast sagt Erfolg, aber nach Refresh ist nichts da.

Nutze Statuscodes als klares Signal und halte die Response-Form ehrlich.

Ein einfaches, vorhersehbares Muster

Wähle Regeln, die ihr immer befolgt:

  • 201 Created wenn ein neuer Datensatz angelegt wurde, und gib die neue Ressource im Body zurück.
  • 200 OK für Lese- und Update-Operationen, mit einem JSON-Body, der den gespeicherten Zustand repräsentiert.
  • 204 No Content nur wenn wirklich kein Body zurückgegeben wird (und der Client keine neuen Daten braucht).
  • 422 Unprocessable Entity für Validierungsprobleme (Feldfehler, die der Nutzer beheben kann).
  • 409 Conflict für Duplikate oder Versionskonflikte (die Anfrage ist gültig, kann aber nicht so angewendet werden).

Ein 200 OK mit einem { error: ... }-Objekt ist eine Falle. Viele Frontends prüfen nur response.ok oder den HTTP-Code. Die UI zeigt Erfolg, während das Backend die Speicherung verweigert.

Idempotenz, Duplikate und „nochmal versuchen“-Verhalten

Wenn Nutzer den Save doppelklicken, während ein Save läuft aktualisieren oder nach einem Timeout erneut versuchen, braucht ihr klare Regeln für Duplikate.

Verwende 409 wenn derselbe eindeutige Wert bereits existiert (z. B. E-Mail) oder wenn optimistisches Locking fehlschlägt (stale updatedAt oder version). Verwende 422 wenn die Payload selbst ungültig ist (fehlende Pflichtfelder, falsches Format).

Was eine erfolgreiche Speicherung zurückgeben sollte

Selbst bei Updates: gib die kanonischen Daten zurück, die der Server gespeichert hat, nicht einfach ein Echo dessen, was der Client geschickt hat. Eine gute Save-Antwort enthält meist:

  • id
  • updatedAt (oder version)
  • die normalisierten Felder (getrimmte Strings, berechnete Defaults)
  • server-generierte Werte (Slugs, Status)

Beispiel: Wenn das Frontend " Acme " sendet, sollte die Antwort "Acme" zurückgeben. So stimmt die UI sofort mit der Realität überein und ihr entdeckt Vertragsprobleme früh. Teams bringen oft kaputte KI-generierte APIs zu FixMyMess, bei denen eine "erfolgreiche" Antwort tatsächlich einen abgelehnten Save hinter einem 200 versteckt.

Paginierungsformate, die im Stack stabil bleiben

Paginierung ist ein Vertrag, kein Implementierungsdetail. Wenn Frontend und Backend sich über die Form uneinig sind, bekommst du leere Tabellen, doppelte Zeilen oder ein "Load more", das nie endet. Diese Vertragsabweichungen sind bei KI-generierten APIs häufig, weil UI und Server separat gescaffoldet wurden.

Wähle einen Paginierungsstil und benenne ihn klar

Die schnellste Vermeidung von Verwirrung ist, einen Stil zu wählen und die genauen Request-Parameter aufzuschreiben.

  • page + pageSize: einfach für Seitenzahlen, kann aber langsam sein, wenn die DB viel zählen und überspringen muss.
  • offset + limit: leicht umzusetzen, aber Inserts/Deletes können zu Duplikaten oder fehlenden Zeilen führen.
  • cursor: ideal für Infinite Scroll, stabil bei Änderungen, braucht aber ein Cursor-Token und eine strikte Sortierung.

Wenn ihr euch entschieden habt, bleibt konsistent über Endpunkte. Eine UI, die page=3&pageSize=20 baut, funktioniert nicht korrekt, wenn ein Endpoint still offset=40&limit=20 erwartet.

Friere die Response-Form ein, die die UI liest

Entscheide dich für die exakten Felder, auf die das Frontend sich verlassen kann. Ein sicherer Default ist: items plus eine Möglichkeit zu wissen, ob mehr Daten existieren. Totals sind optional und können teuer sein.

Ein sehr häufiger Drift ist, dass das Backend { data: [...] } zurückgibt, während die UI [...] (oder items) erwartet. Die Anfrage ist erfolgreich, die UI rendert nichts, und niemand sieht einen Fehler.

Um Seiten-Neuordnungen zu vermeiden, sperre diese Regeln in den Vertrag:

  • Immer eine deterministische Sortierung verlangen (z. B. sort=createdAt:desc).
  • Filter vor dem Paging anwenden und nach Möglichkeit die angewendeten Filter zurückgeben.
  • Bei Cursor-Paging basiere den Cursor auf denselben Sort-Feldern, die du zurückgibst.
  • Bei leeren Zuständen items: [] mit hasMore: false zurückgeben.

Wenn FixMyMess kaputte Prototypen auditiert, ist instabiles Paging oft die verborgene Ursache für "nichts wurde gespeichert"-Meldungen, weil der gespeicherte Datensatz existiert, aber nie in der Listenansicht erscheint, die der Nutzer direkt nach dem Speichern prüft.

Häufige Fallen, die stille Save-Fehler verursachen

Make Your API Contract Explicit
Fix DTOs, status codes, and responses so your UI can trust every save.

Ein Formular kann gesund aussehen und trotzdem scheitern, weil UI und API in kleinen, leicht zu übersehenden Punkten uneins sind. Diese Abweichungen werfen oft keinen offensichtlichen Fehler, deshalb nimmt man an, die DB wäre fehlerhaft, obwohl die Request-Form schuld ist.

Eine typische Falle ist stille JSON-Koerzierung. Das Frontend schickt einen String, wo die API eine Zahl erwartet, oder einen leeren String für ein nullable Feld. Manche Server droppen Felder, die sie nicht parsen können, und der Save schlägt später fehl, weil das fehlende Feld erforderlich ist.

Ein weiterer Klassiker: Felder, die in der UI optional wirken, sind zum Speichern erforderlich. Multi-Tenant-Apps brauchen oft tenantId, orgId oder userId. Wenn diese normalerweise aus dem Auth‑Kontext kommen, kann ein kleiner Auth‑Bug sie leer machen, ohne dass sich das Formular ändert.

Daten (Dates) verursachen subtile Fehler. Ein Datepicker könnte "01/02/2026" senden, während die API ISO erwartet wie "2026-02-01". Timezones verschieben Werte. Du speicherst "14. Jan" aber der Server legt aufgrund UTC "13. Jan" ab, und es sieht aus, als hätte der Save nicht funktioniert.

Auth-Kontexte sind tückisch. Die UI zeigt dich als eingeloggt, weil ein Token vorhanden ist, aber die API behandelt die Anfrage als anonym, weil der Header fehlt, das Cookie blockiert ist oder das Token abgelaufen ist.

Optimistische UI-Updates können all das verbergen. Der Bildschirm aktualisiert sich, als ob der Save erfolgreich wäre, während das Backend die Anfrage abgelehnt hat.

Achte auf diese Signale:

  • Network-Tab zeigt 200, aber der Response-Body sagt "error" oder liefert einen unveränderten Datensatz.
  • API liefert 204 und die UI kann nicht bestätigen, was gespeichert wurde.
  • Erforderliche IDs fehlen in der Payload, aber es wird kein Feldfehler angezeigt.
  • Ein Datum sieht in der UI richtig aus, ist aber in der DB anders.
  • Die UI updated, bevor der API-Call abgeschlossen ist.

Bei Audits von KI-generierten Apps findet FixMyMess oft zwei unterschiedliche DTO-Shapes auf verschiedenen Screens, sodass auf einer Seite ein Save klappt und auf einer anderen mit denselben Formularfeldern still fehlschlägt.

Kurze Checkliste vor dem Release

Ein Formular, das "abschickt", ist nicht dasselbe wie Daten, die gespeichert sind. Vor dem Release mache einen schnellen Vertrags-Check, der den ganzen Pfad abdeckt: UI-Felder, API-DTOs, Validierung und was der Server zurückgibt.

Beginne damit, echte Beispiele zu sammeln, nicht nur Typen. Lege eine Beispiel-Anfrage und eine Beispiel-Antwort neben den Code und bestätige, dass sie dem entsprechen, was der Server tatsächlich erhält und zurückgibt. Achte auf Namenskonventionen (camelCase vs snake_case), optional vs required Felder und Typ‑Eigenheiten wie Zahlen, die als Strings ankommen.

Hier eine kurze Checkliste, die die meisten stillen Save-Fehler findet:

  • Bestätige, dass Create- und Update-DTOs exakt zu den UI-Feldern passen (Namen, Typen, und welche Felder null oder fehlen dürfen).
  • Sorge dafür, dass jeder fehlgeschlagene Save einen Nicht-2xx-Statuscode zurückgibt plus eine konsistente Error-Body-Form (inkl. allgemeiner Nachricht und Feldfehlern).
  • Stelle sicher, dass die UI Server-Feldfehler auf genau die Input-Namen abbilden kann, die der Nutzer sieht (kein "emailAddress" auf dem Server, wenn das Formular "email" nutzt).
  • Verifiziere, dass alle List-Endpunkte dasselbe Paginierungs-Response-Format nutzen (items-Key, total count, page/limit und wo Meta liegt).
  • Teste ein echtes Create und ein echtes Update end-to-end mit einem echten DB-Eintrag, dann refresh die Seite und bestätige, dass die gespeicherten Werte persistent sind.

Ein praktischer Test: Sende absichtlich ein ungültiges Feld (z. B. ein zu kurzes Passwort). Wenn die UI einen Erfolgstoast zeigt oder das Network 200 anzeigt, während sich nichts geändert hat, lügt irgendwo euer Vertrag.

Wenn du eine KI-generierte App geerbt hast, häufen sich hier die Probleme: DTOs driften, Error-Formate variieren pro Endpoint und Pagination wird pro Screen neu erfunden. Teams wie FixMyMess starten oft mit einem kurzen Audit dieser Verträge, damit Saves vorhersehbar werden, bevor neue Features hinzugefügt werden.

Ein realistisches Beispiel: Save wirkt in Ordnung, aber die Daten sind falsch

Confirm Saves End to End
We trace the exact payload from your form through the API to the database.

Eine typische Geschichte: Ein Signup-Formular zeigt einen Erfolgstoast, aber der Nutzer kann sich später nicht einloggen. Alle vermuten, "Auth ist kaputt", aber der echte Bug ist Request- und Response-Shape.

Das Frontend sendet diese Payload:

{
  "email": "[email protected]",
  "password": "P@ssw0rd!",
  "passwordConfirm": "P@ssw0rd!"
}

Die API erwartet password_confirmation (snake_case) und ignoriert passwordConfirm. Wenn die API außerdem 200 OK mit einem generischen { "success": true } zurückgibt, feiert die UI, obwohl der Server die Bestätigung nie validiert hat und möglicherweise einen falschen Wert gespeichert oder intern abgelehnt hat.

Die Lösung ist langweilig, aber sie wirkt: Einigt euch auf ein DTO und ein Fehlerformat. Entweder den Feldnamen im UI umbenennen oder beide Keys auf dem Server akzeptieren und sie auf dasselbe DTO mappen.

Bei Erfolg gib etwas zurück, das beweist, dass gespeichert wurde:

{
  "id": "usr_123",
  "email": "[email protected]"
}

Verwende 201 Created für einen neuen Nutzer. Bei Validierungsfehlern 422 Unprocessable Entity und Feldfehler, die die UI neben Inputs anzeigen kann:

{
  "errors": {
    "password_confirmation": ["Does not match password"]
  }
}

Ein zweiter Mini-Fall tritt auf Listen-Seiten auf. Das Frontend baut Paginierungs-Controls basierend auf total, aber die API liefert nur einen Cursor und Items. Die UI rendert "Page 1 of 0" oder deaktiviert "Next", obwohl mehr Daten existieren.

Wählt einen Paginierungsstil und bleibt dabei. Wenn ihr Totals wollt, return items und total. Bei Cursor-Paging return items, nextCursor und hasNext, und sag der UI, sie soll nicht nach total fragen.

Nächste Schritte: Vertrag festschreiben und Wiederholung verhindern

Frontend‑Backend‑Vertragsabweichungen passieren wiederholt aus einem Grund: Der Vertrag lebt im Kopf der Leute, nicht an einem prüfbaren Ort. Die Lösung ist langweilig, aber effektiv: schreib ihn auf, teste ihn und behandle Änderungen wie echte Änderungen.

Beginne mit einer einseitigen Vertragsnotiz für die wichtigsten Endpoints (oft: create, update, list). Halte sie in einfacher Sprache und mit konkreten Beispielen.

  • Request-DTO: Feldnamen, required vs optional, Typen und wie leere Werte gesendet werden
  • Response-DTO: was "Erfolg" zurückgibt (gespeicherter Datensatz vs nur eine id)
  • Error-Format: eine einzige Form für Validierungs- und Serverfehler, plus ein paar Beispiel-Payloads
  • Statuscodes: was ihr für Create/Update/NotFound/Validierungsfehler verwendet
  • Pagination: Parameter und Response-Shape (items, total, page, pageSize)

Füge dann eine kleine Menge Contract-Checks für Schlüsselendpunkte hinzu, damit du Brüche am selben Tag findest, an dem sie eingeführt werden. Das können einfache Snapshot-Tests im Backend sein oder ein kleines CI-Skript, das bekannte Payloads postet und auf Response-Shape und Statuscode prüft.

Wähle eine kurze Liste von "darf sich nie still ändern"-Regeln und zwinge sie durch:

  • Validierungsfehler mappt ihr immer auf Felder (inkl. lesbarer Nachricht)
  • Erfolg gibt nie 200 mit einer versteckten Fehlermeldung im Body zurück
  • Pagination gibt immer dieselben Keys zurück, auch bei leeren Items
  • DTOs werden nicht umbenannt ohne Version-Bump oder koordinierte Veröffentlichung

Bevor ihr die UI poliert, standardisiert das API-Error-Format. Sobald das Frontend Feldfehler zuverlässig anzeigen kann, werden die meisten "es hat gespeichert, aber nicht"-Berichte schnell klar.

Wenn euer Code von KI-Tools generiert wurde und die Patterns inkonsistent sind, kann ein fokussierter Audit- und Reparaturdurchlauf der schnellste Weg zu Stabilität sein. FixMyMess bietet kostenlose Code-Audits an und repariert dann Verträge Ende-zu-Ende (DTOs, Validierung, Statuscodes, Pagination), sodass die App in Produktion genauso verhält wie in Demos.