1 Punkte von GN⁺ 2025-05-20 | 1 Kommentare | Auf WhatsApp teilen
  • Zod 4 wurde nach einem Jahr Entwicklung als stabile Version veröffentlicht, behebt Designgrenzen von Zod 3 und verbessert Performance, Bundle-Größe und TypeScript-Kompilierungseffizienz deutlich
  • Zod 3, das im Mai 2021 erschien, wuchs von 2.700 GitHub-Stars und 600.000 wöchentlichen Downloads auf heute 37,8k Stars und 31 Millionen wöchentliche Downloads
  • In Benchmarks wurde das Parsen von Strings 14-mal, von Arrays 7-mal und von Objekten 6,5-mal schneller; auch die tsc-Typinstanziierungen sanken in einem einfachen Beispiel von über 25.000 auf etwa 175
  • Die Kern-Bundle-Größe schrumpfte gzip-komprimiert von 12.47kb bei Zod 3 auf 5.36kb bei der normalen Version von Zod 4; Zod Mini, eine tree-shakebare funktionale API, kommt auf nur 1.88kb
  • Zu den neuen Funktionen gehören JSON-Schema-Konvertierung, Metadaten-Registries, rekursive Objekttyp-Inferenz, Internationalisierung, Pretty-Printing von Fehlern, Template-Literal-Typen, z.stringbool(), ein vereinheitlichter error-Parameter und ein verbessertes z.discriminatedUnion()

Warum Zod 4 eine neue Major-Version ist

  • Zod 4 ist nach einem Jahr aktiver Entwicklung als stabile Version erschienen und implementiert lange gewünschte Funktionen auf einer schnelleren, kleineren und tsc-effizienteren Grundlage
  • Zod v3.0 wurde im Mai 2021 veröffentlicht; damals hatte es 2.700 GitHub-Stars und 600.000 wöchentliche Downloads
  • Heute verzeichnet Zod 37,8k GitHub-Stars und 31 Millionen wöchentliche Downloads
    • Vor 6 Wochen, als die Beta erschien, lagen die wöchentlichen Downloads bei 23 Millionen
  • Nach 24 Minor-Versionen hatte die Zod-3-Codebasis ihre Grenzen erreicht, und für die am häufigsten gewünschten Funktionen und Verbesserungen waren Breaking Changes nötig
  • Zod 4 schließt 9 der 10 öffentlich am häufigsten per Upvote gewünschten Issues

Parsing-Performance und TypeScript-Kompilierungseffizienz

  • Zod 4 bietet Benchmarks, die direkt im Repository ausgeführt werden können
  • Verbesserungen bei der Parsing-Performance:
    • String-Parsen: 14-mal schneller
    • Array-Parsen: 7-mal schneller
    • Objekt-Parsen: 6,5-mal schneller
  • Der Benchmark für Objekt-Parsen verwendet den Moltar validation library benchmark
  • Gemessen mit tsc --extendedDiagnostics sinkt bei der Kompilierung einer einfachen Datei die Zahl der Typinstanziierungen drastisch
    • mit "zod/v3": über 25.000
    • mit "zod/v4": etwa 175
  • Zod 4 vermeidet eine Explosion von Instanziierungen, indem die Generics von ZodObject und anderen Schema-Klassen neu entworfen und vereinfacht wurden
  • Das wiederholte Verketten von .extend() und .omit() benötigte in Zod 3 4000ms zum Kompilieren; ein zusätzlicher Aufruf von .extend() konnte den Fehler "Possibly infinite" auslösen
  • Dasselbe Muster kompiliert in Zod 4 in 400ms und ist damit 10-mal schneller
  • Künftig könnte zusammen mit dem Compiler tsgo die Editor-Performance von Zod 4 auch auf größere Schemas und Codebasen skaliert werden

Bundle-Größe und Zod Mini

  • Die Kern-Bundle-Größe wurde durch Bundling eines einfachen z.boolean()-Validierungsskripts mit rollup verglichen
  • Kern-Bundle-Größe, gzip-komprimiert:
    • Zod 3: 12.47kb
    • Zod 4 normale Version: 5.36kb
  • Das Kern-Bundle der normalen Zod-4-Version ist etwa 57 % kleiner als Zod 3 und wurde auf das 2,3-Fache reduziert
  • Die methodenzentrierte API von Zod ist grundsätzlich schwer zu tree-shaken; selbst ein einfaches z.boolean()-Skript zieht Implementierungen ungenutzter Methoden wie .optional() und .array() mit hinein
  • Zod Mini bietet eine funktionale, tree-shakebare API, die zod 1:1 entspricht
    • Wo Zod Methoden verwendet, nutzt Zod Mini typischerweise Wrapper-Funktionen
    • Parsing-Methoden sind in Zod und Zod Mini identisch
    • Über die universelle Methode .check() lassen sich Verfeinerungen (Refinements) hinzufügen
  • Normales Zod bleibt für die meisten Anwendungsfälle die empfohlene Wahl; Projekte mit ungewöhnlich strengen Bundle-Größenbeschränkungen können Zod Mini in Betracht ziehen
  • Bei Verwendung von zod/mini beträgt die gzip-Bundle-Größe 1.88kb
    • Gegenüber Zod 3 entspricht das einer Reduktion um 85 % bzw. dem Faktor 6,6
    • Vergleichstabelle: Zod 3 12.47kb, Zod 4 normal 5.36kb, Zod 4 Mini 1.88kb
  • Mehr dazu in der Dokumentation zu zod/mini

Metadaten und JSON Schema

  • Zod 4 führt ein neues System ein, um stark typisierte Metadaten zu Schemas hinzuzufügen
  • Die Metadaten werden nicht im Schema selbst gespeichert, sondern in einer Schema-Registry, die Schema und typisierte Metadaten verknüpft
  • Mit z.registry() lässt sich eine Registry erstellen; über die Methode .register() eines Schemas ist auch eine bequeme Registrierung möglich
  • Zod exportiert die globale Registry z.globalRegistry, die gemeinsame JSON-Schema-kompatible Metadaten entgegennimmt
  • Die Methode .meta() fügt ein Schema bequem zu z.globalRegistry hinzu
  • Aus Kompatibilitätsgründen zu Zod 3 bleibt .describe() erhalten, in Zod 4 wird jedoch .meta() empfohlen
  • Zod 4 bietet eine First-Party-JSON-Schema-Konvertierung über z.toJSONSchema()
  • Die Metadaten aus z.globalRegistry werden automatisch in die JSON-Schema-Ausgabe übernommen
  • Informationen zur Anpassung des erzeugten JSON Schema finden sich in den JSON Schema docs

Neue Schema-Ausdrücke und Typfunktionen

  • Zod 4 kann rekursive Objekttypen korrekt inferieren
    • Rekursive Typen und gegenseitig rekursive Typen lassen sich darstellen
    • Anders als beim Muster für rekursive Typen in Zod 3 ist kein Type Casting nötig
    • Das resultierende Schema ist eine normale ZodObject-Instanz, und der volle Methodenumfang bleibt nutzbar
  • Es wurde ein File schema zur Validierung von File-Instanzen hinzugefügt
  • z.templateLiteral() bildet Template-Literal-Typen aus TypeScript ab
    • Zod-Schematypen, die sich zu Strings umwandeln lassen, speichern intern reguläre Ausdrücke
    • Dazu zählen Strings, String-Formate wie z.email(), Zahlen, Boolean, BigInt, Enum, Literal, Undefined/Optional, Null/Nullable und andere Template Literals
    • Der Konstruktor von z.templateLiteral verbindet diese zu einem einzigen Super-Regex
    • String-Formate wie z.email() werden korrekt erzwungen, benutzerdefinierte Refinements jedoch nicht
    • Mehr dazu in den template literal docs
  • Neue Zahlenformate für Integer fester Breite und Fließkommatypen wurden hinzugefügt
    • Sie geben ZodNumber-Instanzen zurück, bei denen passende inklusive Minimum-/Maximum-Beschränkungen bereits gesetzt sind
  • Es wurden auch BigInt-Zahlenformate hinzugefügt, die Bereiche oberhalb dessen abdecken, was sich mit JavaScript-number sicher darstellen lässt
    • Sie geben ZodBigInt-Instanzen zurück, bei denen passende inklusive Minimum-/Maximum-Beschränkungen bereits gesetzt sind
  • z.literal() kann jetzt optional mehrere Werte annehmen

Fehler, Internationalisierung und String-Formate

  • Zod 4 führt eine neue locales-API zur globalen Übersetzung von Fehlermeldungen in mehrere Sprachen ein
  • Eine vollständige Liste der unterstützten Locales steht unter Customizing errors und wird bei neuen unterstützten Sprachen aktualisiert
  • Zod implementiert die Top-Level-Funktion z.prettifyError, um ZodError in einen benutzerfreundlich formatierten String umzuwandeln
    • Das aktuelle Format ist nicht konfigurierbar
    • Es kann sich künftig ändern
    • Wer bereits zod-validation-error nutzt, kann dies weiterhin tun
  • Alle String-Formate wie etwa Email wurden zu Top-Level-Funktionen des z-Moduls hochgestuft
    • Das ist kompakter und besser für Tree Shaking
    • Entsprechende methodenbasierte APIs wie z.string().email() können weiter genutzt werden, sind aber deprecated
    • Sie sollen in der nächsten Major-Version entfernt werden
  • Die API z.email() unterstützt benutzerdefinierte reguläre Ausdrücke
    • Es gibt kein einziges kanonisches Regex für Email-Adressen; je nach Anwendung können strengere oder lockerere Regeln sinnvoll sein
    • Zod exportiert der Bequemlichkeit halber einige gebräuchliche Regexe

Boolean-Konvertierung und vereinfachte Fehleranpassung

  • Das bisherige z.coerce.boolean() ist eine einfache API, die falsy-Werte in false und truthy-Werte in true umwandelt
    • false, undefined, null, 0, "", NaN usw. werden zu false
    • Dieses Verhalten ist konsistent mit anderen z.coerce-APIs
  • Für eine differenziertere Boolean-Coercion im Stil von Umgebungsvariablen führt Zod 4 z.stringbool() ein
  • Die meisten Breaking Changes in Zod 4 betreffen die API zur Fehleranpassung
  • Die Fehleranpassung wurde auf einen einzigen Parameter error vereinheitlicht
    • message wird durch error ersetzt
    • Der Parameter message wird weiterhin unterstützt, ist aber deprecated
    • invalid_type_error und required_error werden durch error in Funktionssyntax ersetzt
    • Auch errorMap wird durch error in Funktionssyntax ersetzt

Komponierbarkeit, Refinements und Transformationen

  • z.discriminatedUnion() unterstützt jetzt mehrere Schematypen, die zuvor nicht unterstützt wurden
    • darunter Union und Pipe
    • Eine discriminated union kann als Mitglied einer anderen discriminated union verwendet werden und ist damit komponierbar
  • In Zod 3 wurden Refinements in der Klasse ZodEffects gespeichert, die das ursprüngliche Schema umschloss
    • Diese Struktur machte es unpraktisch, .refine() mit anderen Schema-Methoden wie .min() zu kombinieren
  • In Zod 4 werden Refinements intern im Schema gespeichert, sodass sich .refine() natürlich mit anderen Schema-Methoden kombinieren lässt
  • Die neue Methode .overwrite() beschreibt Transformationen, die den inferierten Typ nicht verändern
    • .transform() erzeugt einen Ausgabetyp, der zur Laufzeit nicht introspektierbar ist, und die Transformationsfunktion ist eine Blackbox, die beliebige Werte zurückgeben kann
    • Deshalb gibt es keine sichere Möglichkeit, sie in JSON Schema zu konvertieren
    • .overwrite() gibt eine Instanz der ursprünglichen Klasse zurück
    • Die Overwrite-Funktion wird als Refinement gespeichert und verändert den inferierten Typ nicht
  • Die bestehenden Methoden .trim(), .toLowerCase() und .toUpperCase() wurden mithilfe von .overwrite() neu implementiert

Eine Grundlage für Library-Autoren

  • Mit der Einführung von Zod Mini wurde das Subpackage zod/v4/core geschaffen, das die Kernfunktionalität enthält, die sich Zod und Zod Mini teilen
  • zod/v4/core erweitert Zod von einer einfachen Library zu einer schnellen Validierungsgrundlage, die in andere Libraries eingebettet werden kann
  • Wer eine Schema-Library bauen möchte, kann sich an den Implementierungen von Zod und Zod Mini orientieren und auf zod/v4/core aufbauen
  • Für Library-Autoren gibt es den Leitfaden For library authors
    • Er behandelt Best Practices für das Aufbauen auf Zod
    • Er beantwortet häufige Fragen dazu, wie sich Zod 3, Zod 4 und Mini gleichzeitig unterstützen lassen

1 Kommentare

 
GN⁺ 2025-05-20
Meinungen auf Hacker News
  • Ich bin der Autor, fragt mich alles. Zur Versionspolitik habe ich die Gründe für diesen Ansatz recht ausführlich zusammengefasst: https://github.com/colinhacks/zod/issues/4371
    Letztlich ist npm nicht dafür ausgelegt, die Situation, in der Zod steckt, gut zu handhaben. Dutzende bis Hunderte Libraries importieren direkt Interfaces/Klassen von Zod und verwenden sie in ihrer öffentlichen API; jedes Mal, wenn Zod eine neue Major-Version veröffentlicht, müssten diese Libraries ebenfalls eine neue Major-Version herausbringen
    Für sich genommen ist das plausibel, aber bei Zod könnte dadurch für alle eine schmerzhafte Versionslawine entstehen, und es sah so aus, als würde ein beträchtlicher Teil des Ökosystems für immer auf v3 festhängen
    Deshalb haben wir, ähnlich wie bei Go, den Weg gewählt, statt einer neuen Version mit Breaking Changes einen neuen Unterpfad zum Paket hinzuzufügen. Im TypeScript-Ökosystem kann eine Library nur zod@^3.25.0 als Peer Dependency haben und aus "zod/v3" sowie "zod/v4" das Benötigte importieren, um beide Versionen gleichzeitig zu unterstützen

    • Die tsc-Performance-Verbesserungen sind bei großen Codebasen besonders willkommen, und die Änderungen an discriminated unions dürften in bestimmten Szenarien, die bisher zu kurz kamen, ebenfalls stark helfen
      Ich verstehe zwar, warum wegen besonderer Umstände eine etwas ungewöhnliche Versionspolitik gewählt wurde, aber aus Sicht eines Teams, das sich keine Sorgen über Zod-Versionskonflikte in transitiven Abhängigkeiten machen muss, wäre ein Paket 4.0.0 ebenfalls wünschenswert
      Wenn ich es richtig verstanden habe, muss man Imports auf "zod/v4" ändern; das ist eine sehr laute Änderung, und es kann lästig werden, per Lint-Regel abzufangen, dass die IDE automatisch aus 'zod' importiert
    • Dieser Ansatz ist sehr vernünftig. Er bedeutet auch, dass Sicherheitsupdates für ältere Versionen innerhalb desselben Codebase-Releases mitgeliefert werden können
    • Ich bin mobil unterwegs und habe nicht gesehen, ob es schon im Beitrag steht, aber ich frage mich, ob die Arbeit an der Korrektur von .optional() in TypeScript zu den gelösten Top-9-/Top-10-Issues gehört: https://github.com/colinhacks/zod/issues/635
      Das war der größte Schmerzpunkt bei Zod, aber Zod ist trotzdem so gut, dass ich es weiter in Kauf nehme
    • Durch die neuen Funktionen werde ich vermutlich viele lokale provisorische Hacks los. Um Tippfehler bei Formularnamen zu vermeiden, nutze ich eine eigene Version von https://github.com/raflymln/zod-key-parser; ich fand es überraschend, dass so eine Funktion nicht direkt in die Library aufgenommen wurde
      Ich frage mich, ob das als außerhalb des Scopes von Zod betrachtet wird oder ob einfach noch keine Zeit für die Implementierung war. Die zugehörige Diskussion ist unter https://github.com/colinhacks/zod/discussions/2134 zu finden
    • Wenn das bedeutet, dass die früheste Version von Zod 4 3.25.0 ist, klingt das für mich ziemlich unsinnig
      Ich habe seit Monaten die Alpha-Version von Zod verwendet und wollte einfach meine package.json anpassen, um zu upgraden; jetzt werde ich wohl die gesamte Git-Historie durchforsten müssen
      Ich bin dem Projekt sehr dankbar, aber genau dieses Feedback muss ich geben: Das wirkt unnötig schwer gemacht. Ich frage mich, ob man nicht neben 3.x auch 4.x hätte veröffentlichen können
  • Die Aussage, dass „Zod 4 zur Vereinfachung der Migration als Teil des Releases zod@3.25 zusammen mit Zod 3 ausgeliefert wird und über den Unterpfad "/v4" importiert werden soll“, wirkt wie ein Zeichen dafür, dass npm Dependency Management völlig chaotisch ist
    Peer Dependencies sind so kaputt, dass v4 so tun muss, als wäre es v3

    • Ich bin der Autor. Die Gründe für diesen Ansatz habe ich hier recht ausführlich beschrieben: https://github.com/colinhacks/zod/issues/4371
      Es stimmt, dass npm die Situation, in der Zod steckt, nicht gut bewältigt. Allerdings steht Zod unter der Einschränkung, nahezu keine Libraries einzubinden. Dutzende bis Hunderte Libraries importieren Interfaces/Klassen direkt aus "zod" und verwenden sie in ihrer öffentlichen API
      Solche Libraries müssten jedes Mal, wenn Zod die Major-Version erhöht, ebenfalls eine neue Major-Version veröffentlichen, und bei Zod könnte das eine Versionslawine auslösen, die für alle schmerzhaft wäre. Ehrlich gesagt sah es so aus, als würde ein beträchtlicher Teil des Ökosystems für immer auf v3 festhängen
      Deshalb haben wir, ähnlich wie bei Go, statt für jedes Release mit Breaking Changes eine neue Paketversion herauszugeben, einen neuen Unterpfad hinzugefügt. Im TypeScript-Ökosystem kann man mit einer einzigen Peer Dependency zod@^3.25.0 gleichzeitig "zod/v3" und "zod/v4" unterstützen
    • Die Schlussfolgerung „Peer Dependencies sind kaputt, also tut v4 so, als wäre es v3“ scheint mir nicht richtig. Ich sehe es eher so, dass Zod v4 in v3 enthalten ist, damit Nutzer schrittweise umziehen können
      Also die Verwendungen nach und nach zu import ... from 'zod/v4' refaktorisieren und danach vollständig auf v4 hochgehen
    • Es fühlt sich so an, als würde alles Negative Upvotes bekommen. Diese Entscheidung ist weniger eine inhärente Grenze von npm selbst, sondern eher ein praktischer Weg, viele Libraries mit Breaking Changes schrittweise umstellen zu können
    • Vielleicht ist mein Blickfeld verengt, weil ich seit Jahren nur npm nutze, aber ich frage mich, was eine bessere Methode wäre, um von v3 auf v4 nicht in einem großen Schritt, sondern iterativ zu migrieren
    • Das ist kein npm-spezifisches Problem. Dass eine Dependency über eine weitere transitive Dependency von einer alten Version abhängt, passiert praktisch in jedem Ökosystem
      Im Gegenteil: npm bietet sogar mehr Auswege, weil man bei Bedarf mehrere Versionen parallel ausführen oder Teile des transitiven Dependency-Graphen gezielt überschreiben kann
      Welches Paketverwaltungssystem löst dieses Problem? Selbst „stabile“ Plattformen wie Maven behandeln solche Probleme, indem sie Versionen unter neuen Namespaces veröffentlichen, wie damals Apache Commons beim Wechsel von v2 auf v3
  • Eine Frage, die mich schon lange beschäftigt: Ich habe gehört, dass Zod dafür passen könnte, aber selbst nach einem Blick in die Dokumentation ist mir nicht ganz klar, wie man das macht.
    Angenommen, eine Server-API gibt präzisere Typen zurück, als sie ausdrücken kann. Zum Beispiel gibt api/:postid/author einen User zurück, aber das kann ein normaler User oder ein anonymer User sein, sodass Felder wie username und location als null kommen können. In diesem Fall möchte man das User-Objekt vielleicht als discriminated union darstellen.
    Auch Objekte von anderen Endpunkten müssen eventuell typtransformiert werden. Ein User enthält manchmal Post[], und wenn ein Post ein Moderator-Beitrag ist, kann er spezielle Eigenschaften haben.
    Früher habe ich Funktionen wie normalizeUser() und normalizePost() geschrieben, aber das wurde schnell unübersichtlich. Weil jeder Endpunkt andere Teilmengen des User/Post-Modells zurückgab, hatte ich am Ende etwa fünf Varianten von normalizePost, und das war viel zu chaotisch. Ich frage mich, wie man solche Probleme normalerweise löst.

    • Man kann vermutlich discriminated unions verwenden.
      Also etwa success- und failed-Objekte anhand eines diskriminierenden Felds wie status trennen. Wenn es viele spezielle Moderator-Eigenschaften gibt, man sie aber nicht alle aufzählen oder prüfen möchte, kann man ein Passthrough-Verhalten definieren.
      Auch wenn mehrere Methoden unterschiedliche Schemas haben: Wenn es gemeinsame Teile gibt, kann man ein gemeinsames Objekt-Schema erstellen und darauf Objekte mit zusätzlichen Feldern aufbauen. Bei vielen Möglichkeiten bleibt es trotzdem unordentlich, aber wenn die Situation ohnehin unordentlich ist, kann ein Validator diese Unordnung zumindest validieren.
    • Idealerweise sollte es eine Single Source of Truth dafür geben, welche Formen ein User annehmen kann. Das könnte eine discriminated union wie User und AnonymousUser sein.
      Wenn es kein Full-Stack-TypeScript ist, kann man zum Beispiel in einem Python-Backend die verschiedenen User-Formen als Pydantic-Modelle und Unions definieren und dann über OpenAPI-/GraphQL-Schema-Generierung und Codegenerierung TypeScript-Client-Typen erzeugen.
    • Ohne mehr über den konkreten Use Case zu wissen, lässt sich das schwer lösen, aber wenn man allen Typen in der Union eine diskriminierende Eigenschaft wie "user_type" hinzufügt, lassen sich Normal- und Sonderfälle leichter behandeln.
      Wenn man zum Beispiel user.user_type === 'authenticated' prüft, weiß das Typsystem danach, dass user.name verwendet werden kann.
    • Es hilft in diesem konkreten Fall nicht direkt, aber GraphQL wurde genau für solche Dinge gebaut.
      TypeScript-Bibliotheken für GraphQL-Queries können die Antwortform dynamisch aus der Form der ausgewählten Felder ableiten. Wenn man { user { username posts { text } } } auswählt, hat der Antworttyp posts; wenn man nur { user { username } } auswählt, fehlt die Eigenschaft posts vollständig.
    • Der Server muss die Typen ausgeben. Typen auf dem Client von Hand zu schreiben ergibt keinen Sinn.
      Der Server weiß, welche Daten er sendet, also sollte er diese Information dem Client als Metadaten bereitstellen. Praktisch kann das etwa so aussehen: Ein Python-Backend verwendet Pydantic-Schemas, daraus wird automatisch eine OpenAPI-Spezifikation erzeugt, und der Client generiert daraus Typen.
  • Zod ist deutlich besser als die Alternativen, die ich gesehen habe, aber manchmal fühlt sich schon die Tatsache, dass diese explizite Validierung nötig ist, wie ein Scheitern dessen an, wo die moderne Webentwicklung gelandet ist.
    In der Full-Stack-Entwicklung ist es extrem frustrierend, dieselbe Form auf mehrere Arten beschreiben zu müssen. Man braucht JS-Eingabevalidierung, Swagger für API-Definitionen, serverseitige Eingabevalidierung, ein ORM für Schema-Konformität und dann noch separate TypeScript-Definitionen für Server und Client; das wird ermüdend.

    • Dass TypeScript darauf beharrt, nur ein System für statische Prüfung/Compile-Time zu sein, ist eine bedauerliche Wunde für das ganze Ökosystem. Ich will nicht, dass TypeScript ein Runtime-Checker wird, aber ich wünschte, es würde Typdaten ausgeben, die für Klassen, Funktionen und Objekte nützlich verwendbar sind.
      TypeScript fühlt sich wie die beste Single Source of Truth an, die wir haben, aber in der Praxis definieren Tools, die Reflection versuchen, jeweils ihr eigenes Modell und ihre eigenen Builder neu. Allein große Bereiche, in denen Reflection zentral ist, gibt es fünf, und für jeden mehrere Implementierungen.
      Das alte TypeScript-Type-Emission-Tool reflect-metadata stellt einige Runtime-Typinformationen bereit, zielt aber auf sehr alte Decorator- und Metadata-Spezifikationen ab: https://www.npmjs.com/package/reflect-metadata
      Vielleicht stehen wir ja am Anfang einer Vereinheitlichung. Das Projekt Standard Schema scheint von den meisten großen Validierungsbibliotheken unterstützt zu werden. Das auf API-Definitionen oder ORM-Tools auszuweiten, ist allerdings noch sehr früh.
      https://github.com/standard-schema/standard-schema?tab=readm...
    • Genau das ist im Grunde der Kern solcher Tools: einmal definieren und alles darunter dynamisch daraus generieren.
      Ändert man etwas einmal im Zod-Schema, wird es zusammen mit der Typprüfung durch die ganze App propagiert. Das Zod-Schema wird damit zur Single Source of Truth.
    • Normalerweise muss man Daten nicht überall neu definieren. Es gibt extrem viele Tools, die zu Zod oder aus Zod heraus konvertieren.
      Wenn bereits ein JSON-Schema-/Swagger-Schema existiert, kann man daraus Zod generieren. Wenn man ein TypeScript-ORM verwendet, gibt es mit ziemlicher Sicherheit einen Zod-Generator.
      Ehrlich gesagt ist Zod so weit verbreitet, dass es für mich zur vereinheitlichten Schema-Definition geworden ist, auf die andere Teile des Stacks aufbauen können. Weil man die Typen direkt definiert, halten Entwickler sie tatsächlich aktuell. Die Swagger-Dokumentation in Unternehmen hinkt Änderungen immer hinterher.
    • Alle APIs sind experimentell und in ständigem Wandel und Weiterentwicklung, und fürs Web gilt dasselbe.
      Der Status quo ist nur dann in Ordnung, wenn man mit Kunden und Arbeitgebern zu tun hat, die einfach nur Ergebnisse wollen und sich nicht für die Vorgehensweise interessieren. In diesem Kontext erhöhen diese unglücklichen Komplexitätsschichten immerhin die abrechenbaren Stunden.
    • Viele Leute, die sich darüber beschweren, dass moderne Webentwicklung komplex ist, wären vermutlich auch entsetzt über den Vorschlag, all das einfach durch TypeScript und Zod zu ersetzen.
      Natürlich unter der Voraussetzung, dass Zod die von ihnen gewählte TypeScript-Bibliothek für Schema-Definition und Validierung ist.
  • Zod 4 sieht gut aus, aber selbst mit den neuesten Verbesserungen ist ArkType immer noch um einen einstelligen Faktor schneller.
    Für Abwärtskompatibilität und Syntaxkompatibilität ist es manchmal schwierig, etwas so viel schneller zu machen wie eine Bibliothek mit komplettem Neustart. Ich habe in einem aktuellen Projekt all diese Tools analysiert und mich teilweise aus diesem Grund für ArkType entschieden. Außerdem fühlte sich die TypeScript-Nutzung besser an.

    • Ich habe mir die Geschwindigkeitskennzahlen angesehen und frage mich, ob du erklären kannst, wo diese Geschwindigkeit einen Unterschied macht.
      Wir verwenden Zod nur für Formularvalidierung, daher denke ich: „Warum ist das wichtig?“ Vielleicht gibt es Fälle, in denen Leute Dinge wie Input-Messages für High-Throughput-APIs validieren, wodurch Performance wichtiger wird.
    • Mich würde interessieren, warum du ArkType gegenüber TypeBox gewählt hast.
    • Interessanterweise habe ich mir dieses Tool bei der Recherche gar nicht angesehen. Ich habe ebenfalls besonders auf das TypeScript-Gefühl geachtet.
      Trotzdem glaube ich nicht, dass ich von Zod wechseln werde.
    • ArkType ist ein Albtraum beim Schreiben, Zod ist angenehm zu schreiben.
  • Glückwunsch an das Zod-Team zum neuen Release. Auch auf die Gefahr hin, zu negativ zu klingen: Bei der Zahl der Breaking Changes im Migrationsleitfaden läuft es mir kalt den Rücken hinunter.
    Für Projekte, die stark von Zod abhängen, wirkt das wie eine mühsame Aufgabe, die viel Konzentration und Zeit von Entwicklern erfordert. Das kann ich besonders gut nachvollziehen, weil ich beruflich einige 4–5 Jahre alte Frontend-Projekte gewartet habe.
    Große React-Projekte hängen typischerweise von vielen Bibliotheken ab, und wenn jede davon große Änderungen bringt, noch dazu mit unzureichender Dokumentation, wird man sehr schnell überwältigt. Das ist einer der Aspekte, die ich an der Arbeit mit JavaScript am meisten hasse: Alles aufeinander abzustimmen und dauerhaft reibungslos am Laufen zu halten, fühlt sich wie ein endloser Anstieg an.

    • Stimme völlig zu. Wir betreiben mehrere große Next.js-Apps und mussten im letzten Jahr die großen Cache-Änderungen von Next.js 14 → 15, Next.js pages → app router, React 18 → 19, Eslint 8 → 9 und Tailwind 3 → 4 durchmachen.
      Ehrlich gesagt war es ein Albtraum, und ich wünschte, wir hätten es stattdessen mit Django gebaut. Entgegen meinen Erwartungen gehörte die Migration von Tailwind 3 → 4 zu den schmerzhaftesten.
    • Genau deshalb haben sie sich für einen Dual-Delivery-Ansatz mit einer separaten mini-Edition entschieden. So wird eine schrittweise Migration einfacher, ohne den Paketmanager anzufassen.
      Nutzer, die sich nicht für die mini-Edition interessieren, müssen sich darum nicht kümmern. Allerdings sind die Tree-Shaking-Vorteile der mini-Edition so groß, dass sie die Entwicklung von Alternativen angetrieben haben, und Zod musste das annehmen oder an Bedeutung verlieren.
    • Nimm einfach ein LLM.
  • Als Full-Stack-Entwickler, der selbst ein SaaS mit Tausenden Nutzern in Produktion betreibt, merke ich manchmal erfreut, wie viele Probleme ich gar nicht kenne, weil ich mich entschieden habe, keine SPA zu bauen und kein JS-Frontend-Framework zu verwenden.
    Ich habe nicht einmal daran gedacht, dass man etwas wie Zod/ArkType brauchen könnte, höre zum ersten Mal davon und werde es auch nicht verwenden.
    Es ist erstaunlich, wie viel Aufwand, Komplexität und Tooling in den Bau einer SPA fließen. Wenn man sich entscheidet, keine SPA zu bauen, existieren ganze Problemklassen einfach nicht. Stattdessen nutzt man den Browser so, wie er ursprünglich gedacht war: mit vollständigen Seiten-Reloads und Fokus auf Backend-Entwicklung, und fügt gelegentlich mit einem reinen Backend-Framework wie Laravel Livewire etwas Reaktivität hinzu.
    Alles ist einfacher, von Zugriffskontrolle über Validierung bis hin zu State Management. Die App ist schnell, responsiv, modern, SEO-freundlich und bedient Tausende Nutzer in Produktion.

    • In zwei großen Projekten mit mehr als fünf Teams haben wir Zod und Protobuf verwendet, damit Schema-Evolution gut funktioniert. Selbst bei einem schlanken Frontend ist es sinnvoll, im Backend etwas zur Schema-Validierung einzusetzen.
      Am Ende gibt es irgendwo ein Schema. Es kann im Datenbanksystem definiert sein oder in ORM-Modellen. In großen Projekten können Schemas im Datenbanksystem oder ORM jedoch sehr schwierige Probleme verursachen, daher verwenden wir Zod und Protobuf. Ich denke, Protobuf könnte ebenfalls vollständig durch Zod oder etwas Ähnliches ersetzt werden.
      Der britische Post-Office-Skandal betraf ein IT-System namens Horizon: https://en.wikipedia.org/wiki/British_Post_Office_scandal
      Nachdem ich die Details gelesen habe, scheint mir, dass Schema-Validierung wie bei Zod dazu hätte beitragen können, diesen Skandal zu verhindern. Auch der technische Anhang der Gerichtsunterlagen zur Sammelklage erwähnt das Fehlen der Nutzung von Schemas: https://en.wikipedia.org/wiki/Bates_%26_Others_v_Post_Office...
    • Zod hat nichts mit SPAs zu tun und ist auch kein reines Frontend-Tool. Validierst du Eingaben im Code nicht?
    • Wenn man die Augen schließt, hören ganze Problemklassen manchmal auf zu existieren.
    • Mich würde interessieren, wie du serverseitige Validierung machst.
      Im Moment klingt es so, als würdest du defensiv jede Menge if-Anweisungen schreiben. Schlimmer noch: Vielleicht verlässt du dich nur auf clientseitige HTML-Formularvalidierung.
    • Wir haben sehr viele reine Backend-Zod-Schemas, die als Teil unserer Datenpipeline verwendet werden. Daher wirkt es nicht wie ein Tool, das nur existiert, um SPA-Probleme zu lösen.
  • Ich bin kein Experte und daher vorsichtig, aber ich dachte, JSON Schema könnte eine gute Wahl sein, weil es schemabasiert ist und sich Validatoren auch in anderen Sprachen als TypeScript implementieren lassen.
    https://ajv.js.org ist eine dieser JSON-Schema-Bibliotheken. Mich würde interessieren, wie sie im Vergleich zu Zod abschneidet.

    • Zod ist kein Tool nur für JSON-Validierung. Es kann Objekte validieren, die sich ohne Transformation nicht als JSON darstellen lassen, zum Beispiel Datumswerte oder Klasseninstanzen.
      Wenn man möchte, kann man es auch als JSON-Transformer verwenden. Man kann zum Beispiel ein Schema schreiben, das einen String entgegennimmt, prüft, ob er ein ISO-Datumsstring ist, und ein Date-Objekt ausgibt.
    • Zod 4 unterstützt nativ die Umwandlung von Zod-Schemas in JSON Schema. Früher war das auch schon über Third-Party-Bibliotheken möglich.
      Der wichtige Unterschied sind Preprocessing und refine. In Zod kann man vor der Validierung Callbacks bereitstellen; das ist sehr nützlich, lässt sich aber nicht in JSON ausdrücken. Zum Beispiel ist es überraschend oft hilfreich, vor der Datumsvalidierung MM/DD/YYYY in DD/MM/YYYY umzuwandeln.
    • In solchen Fällen ist JSON Schema eine gute Wahl, und TypeBox ist eine ordentliche Möglichkeit, es zu erzeugen.
      Für die Validierung kann man AJV verwenden oder das eigene Schema-Validierungssystem von TypeBox. Letzteres ist deutlich schneller, unterstützt aber nur synchrone Validierung; AJV bietet Optionen für asynchrone Validierung, etwa Datenbankprüfungen.
    • Für sprachübergreifende Validierung ist JSON Schema die richtige Wahl. Wenn man es mit OpenAPI umgibt, bekommt man auch gleich brauchbare API-Dokumentation gratis dazu.
  • Die negativen Reaktionen rund um dieses Thema überraschen mich.
    Ich habe eine frühe Version von Zod v4 getestet und mochte die neue API, war aber sehr besorgt darüber, wie der Migrationspfad aussehen würde. Ich hatte sogar überlegt vorzuschlagen, es unter einem ganz neuen Paketnamen zu veröffentlichen.
    Der Ansatz des Autors ist aber clever. Er ermöglicht Leuten wie mir, v4 sofort einzuführen, ohne darauf warten zu müssen, dass alle Abhängigkeiten aktualisiert werden.

    • Das ist ein guter Weg. Bei etwas wie Validierung gibt es keine praktikable Möglichkeit für eine Alles-oder-nichts-Migration.
  • Ich habe gerade erst begonnen, Zod in einem neuen Projekt einzuführen, und der Zeitpunkt hätte nicht besser sein können.
    Nach dem, was ich jetzt sehe, hätte ich für den Umstieg auf v4 wirklich sehr viel ändern müssen.