VeeValidate kombiniert mit Zod für Vue-Formular-Validierung
AI generated
{ }
Vue · VeeValidate · Zod
VeeValidate + Zod
Ein Schema für Validierungsregeln und TypeScript-Typen

Ein Zod-Schema beschreibt gleichzeitig, welche Werte ein Formularfeld annehmen darf und welchen TypeScript-Typ das Ergebnis nach erfolgreicher Validierung hat. Kombiniert mit VeeValidate über einen Zod-Resolver entsteht eine einzige Quelle der Wahrheit für Formulare, die sonst leicht zwischen Validierungslogik und Typdefinitionen auseinanderdriftet.

14 Min. Lesezeit VeeValidate Zod

1. Das Problem doppelt gepflegter Validierungsregeln

Reines VeeValidate ohne Schema-Bibliothek definiert Validierungsregeln meist direkt an jedem Feld, entweder über eingebaute Regeln wie required und email oder über individuelle Validator-Funktionen. Der TypeScript-Typ des Formularwerts wird davon unabhängig separat definiert, meist als eigenes Interface oder eigener Typ, der von Hand mit den Validierungsregeln synchron gehalten werden muss. Ändert sich eine Regel, etwa eine neue Mindestlänge für ein Passwortfeld, muss diese Änderung an zwei Stellen nachgezogen werden, der Validierungslogik und der Typdefinition, was leicht auseinanderläuft.

Zod löst dieses Problem, indem ein einziges Schema sowohl die Validierungsregeln als auch die TypeScript-Typinferenz übernimmt. Aus einem Zod-Schema lässt sich über z.infer<typeof schema> automatisch der passende TypeScript-Typ ableiten, sodass Validierungsregeln und Typdefinition zwangsläufig immer synchron bleiben, weil es nur noch eine einzige Quelle gibt, aus der beides erzeugt wird.

2. Ein Zod-Schema als einzige Quelle der Wahrheit definieren

Ein typisches Zod-Schema für ein Registrierungsformular kombiniert primitive Validierungen wie z.string().min(2) für einen Namen mit spezialisierteren Validierungen wie z.string().email() für eine E-Mail-Adresse. Zod bietet außerdem Mechanismen für feldübergreifende Validierung über .refine(), etwa um zu prüfen, ob ein Passwort-Bestätigungsfeld mit dem eigentlichen Passwortfeld übereinstimmt, was in reinem VeeValidate meist eine eigene, manuell geschriebene Cross-Field-Validierungsfunktion erfordert hätte.

Sobald das Schema einmal definiert ist, lässt es sich sowohl für die Formularvalidierung selbst als auch für jede andere Stelle im Code verwenden, die denselben Datentyp erwartet, etwa eine API-Funktion, die die validierten Formulardaten an ein Backend sendet. Diese Wiederverwendbarkeit über die reine Formularvalidierung hinaus ist einer der praktischen Hauptvorteile gegenüber isolierten VeeValidate-Regeln, die nur im Kontext des jeweiligen Formulars Sinn ergeben.


// src/schemas/registerSchema.ts
import { z } from 'zod'

export const registerSchema = z
  .object({
    name: z.string().min(2, 'Name muss mindestens 2 Zeichen haben'),
    email: z.string().email('Bitte eine gueltige E-Mail-Adresse angeben'),
    password: z.string().min(8, 'Passwort muss mindestens 8 Zeichen haben'),
    passwordConfirm: z.string(),
  })
  .refine((data) => data.password === data.passwordConfirm, {
    message: 'Passwoerter stimmen nicht ueberein',
    path: ['passwordConfirm'],
  })

// Automatisch abgeleiteter TypeScript-Typ, immer synchron zum Schema
export type RegisterFormData = z.infer<typeof registerSchema>

3. VeeValidate-Feldkomponenten mit dem Zod-Resolver verbinden

Die Brücke zwischen Zod und VeeValidate stellt das Paket @vee-validate/zod mit seiner Funktion toTypedSchema() her. Diese Funktion wandelt ein Zod-Schema in ein Format um, das VeeValidates useForm()-Composable als validationSchema akzeptiert. Ab diesem Punkt übernimmt VeeValidate die eigentliche Formular-Orchestrierung, also das Tracking von Feldwerten, Touched- und Dirty-Zuständen sowie das Auslösen der Validierung bei Blur- oder Submit-Events, während Zod ausschließlich für die inhaltliche Validierungslogik zuständig bleibt.

Innerhalb der Formularkomponente werden einzelne Felder über VeeValidates useField() oder die deklarative <Field>-Komponente eingebunden, wobei der Feldname exakt dem Schlüssel im Zod-Schema entsprechen muss, damit VeeValidate die passende Teilvalidierung dem richtigen Feld zuordnen kann. Diese Kopplung über den Feldnamen ist der einzige Punkt, an dem Schema und Template synchron gehalten werden müssen, alles andere übernimmt der Resolver automatisch.


<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { registerSchema, type RegisterFormData } from '@/schemas/registerSchema'

const { handleSubmit, errors, defineField } = useForm<RegisterFormData>({
  validationSchema: toTypedSchema(registerSchema),
})

const [email, emailAttrs] = defineField('email')
const [password, passwordAttrs] = defineField('password')

const onSubmit = handleSubmit((values) => {
  // values ist bereits vollständig typisiert als RegisterFormData
  console.log(values.email)
})
</script>

<template>
  <form @submit="onSubmit">
    <input v-model="email" v-bind="emailAttrs" type="email" />
    <span>{{ errors.email }}</span>

    <input v-model="password" v-bind="passwordAttrs" type="password" />
    <span>{{ errors.password }}</span>
  </form>
</template>

4. Fehleranzeige pro Feld

VeeValidate stellt Fehlermeldungen über das errors-Objekt aus useForm() zur Verfügung, wobei jeder Schlüssel dem jeweiligen Feldnamen entspricht und der Wert die von Zod erzeugte, lesbare Fehlermeldung enthält, sofern das Schema eine eigene Nachricht wie im Beispiel oben definiert. Diese Fehlermeldungen lassen sich direkt im Template unter dem jeweiligen Feld anzeigen, ohne dass eine zusätzliche Übersetzungsebene zwischen Zod und der Anzeige nötig ist.

Für feldübergreifende Validierungen wie den Passwort-Abgleich aus dem Beispiel oben sorgt der path-Parameter in .refine() dafür, dass der Fehler dem richtigen Feld zugeordnet wird, in diesem Fall passwordConfirm, statt als allgemeiner, nicht feldspezifischer Formularfehler zu erscheinen. Ohne diesen Parameter würde Zod den Fehler auf Objektebene melden, was für die feldweise Anzeige in der Praxis meist unpraktisch ist, weil dann nicht klar wäre, unter welchem Eingabefeld die Meldung erscheinen soll.

5. Vergleich zu reinem VeeValidate ohne Schema-Bibliothek

Reines VeeValidate ohne Zod definiert Regeln meist über die eingebaute Regel-Syntax oder individuelle Funktionen direkt am Feld, was für einfache Formulare mit wenigen, unabhängigen Feldern durchaus ausreichend und schnell umzusetzen ist. Der Nachteil zeigt sich bei komplexeren Formularen mit feldübergreifenden Abhängigkeiten, verschachtelten Objektstrukturen oder dem Wunsch nach automatischer TypeScript-Typinferenz, wo reines VeeValidate zusätzliche, manuell gepflegte Typdefinitionen erfordert.

Die Kombination mit Zod lohnt sich besonders, sobald ein Projekt ohnehin bereits TypeScript konsequent nutzt und Formularvalidierung nicht isoliert betrachtet werden soll, sondern im Zusammenhang mit den gleichen Datenstrukturen steht, die auch für API-Aufrufe oder Zustandsverwaltung verwendet werden. Für sehr kleine, einmalige Formulare mit einem einzigen Textfeld kann der zusätzliche Schema-Aufwand dagegen unnötig wirken, dort reicht oft eine einfache eingebaute VeeValidate-Regel völlig aus.

6. Verschachtelte Objekte und Arrays validieren

Ein weiterer Vorteil von Zod zeigt sich bei komplexeren Formularstrukturen mit verschachtelten Objekten oder wiederholbaren Feldgruppen, etwa einer Adressliste mit mehreren Einträgen. Zod bildet solche Strukturen nativ über z.object() und z.array() ab, inklusive Validierung jedes einzelnen Array-Elements gegen ein eigenes Sub-Schema. VeeValidate unterstützt über useFieldArray() dynamische Feldlisten, deren Werte sich direkt gegen ein solches Array-Schema validieren lassen, ohne dass für jede mögliche Array-Länge eigene Validierungsregeln geschrieben werden müssten.

In reinem VeeValidate ohne Schema-Bibliothek müsste eine vergleichbare verschachtelte Validierung meist über verschachtelte Objektpfade in der Regel-Definition nachgebildet werden, was schnell unübersichtlich wird, sobald mehr als eine Verschachtelungsebene involviert ist. Zod hält diese Struktur dagegen deklarativ und lesbar, weil sich das Schema selbst rekursiv aus kleineren Sub-Schemas zusammensetzen lässt.

7. Asynchrone Validierung mit Zod und VeeValidate

Zod unterstützt über .refine() auch asynchrone Validierungsfunktionen, etwa um zu prüfen, ob ein gewählter Benutzername bereits vergeben ist, indem eine API-Anfrage innerhalb der Refine-Funktion ausgeführt und deren Ergebnis abgewartet wird. VeeValidate erkennt über toTypedSchema() automatisch, dass es sich um eine asynchrone Validierung handelt, und wartet entsprechend auf das Promise, bevor es den Validierungsstatus des Feldes aktualisiert.

Wichtig ist bei asynchronen Refine-Funktionen, die Validierung nicht bei jedem einzelnen Tastendruck auszulösen, weil sonst bei jedem Zeichen eine neue API-Anfrage gesendet würde. VeeValidates Konfigurationsoptionen für den Validierungszeitpunkt, etwa nur bei Blur statt bei jeder Eingabe, lassen sich unabhängig vom Zod-Schema einstellen und sollten bei asynchronen Validierungen bewusst gewählt werden, um unnötige Serverlast zu vermeiden.

8. Typsicheres Submit-Handling

Der größte praktische Gewinn zeigt sich beim Absenden des Formulars: Da handleSubmit() aus VeeValidate den Rückgabetyp des Zod-Schemas kennt, ist das values-Objekt innerhalb der Submit-Callback-Funktion bereits vollständig typisiert, ohne manuelle Typumwandlung oder Casts. Jeder Zugriff auf ein Feld wird vom TypeScript-Compiler geprüft, und ein Tippfehler im Feldnamen fällt bereits beim Kompilieren auf, statt erst zur Laufzeit als undefined-Wert sichtbar zu werden.

Dieser Effekt verstärkt sich, wenn die gleiche RegisterFormData-Typdefinition auch für die Funktion verwendet wird, die die Daten an eine API sendet. Ändert sich das Zod-Schema, etwa weil ein neues Pflichtfeld hinzukommt, meldet der TypeScript-Compiler sofort alle Stellen im Code, die diesen neuen Typ noch nicht berücksichtigen, was Inkonsistenzen zwischen Formular und API-Schicht schon vor dem ersten Testlauf sichtbar macht.

9. Partielle Schemas für Bearbeitungsformulare

Ein häufiger Praxisfall ist ein Bearbeitungsformular, bei dem im Unterschied zum Erstellungsformular nicht jedes Feld verpflichtend ausgefüllt werden muss, weil bereits vorhandene Werte unverändert bleiben dürfen. Statt ein komplett neues Schema von Hand zu pflegen, bietet Zod die Methode .partial(), die aus einem bestehenden Schema automatisch eine Variante ableitet, in der alle Felder optional sind, ohne die ursprünglichen Validierungsregeln für tatsächlich ausgefüllte Felder zu verlieren.

Dieses abgeleitete Teilschema lässt sich genauso über toTypedSchema() an VeeValidate übergeben wie das ursprüngliche vollständige Schema, wodurch ein Erstellungs- und ein Bearbeitungsformular denselben Grundregeln folgen, ohne dass Validierungslogik doppelt gepflegt werden müsste. Ändert sich später eine Regel im Basisschema, etwa eine neue Mindestlänge für den Namen, wirkt sich diese Änderung automatisch auf beide Formulare aus, weil das partielle Schema stets vom aktuellen Basisschema abgeleitet wird und nie unabhängig davon existiert.

Aspekt Reines VeeValidate VeeValidate + Zod
Regel-Definition pro Feld einzeln zentral im Schema
TypeScript-Typen manuell gepflegt automatisch via z.infer
Feldübergreifende Regeln eigene Validator-Funktion .refine() im Schema
Wiederverwendung außerhalb des Formulars kaum möglich Schema überall nutzbar
Verschachtelte Strukturen aufwendig nachzubilden nativ über z.object/z.array

Mironsoft

Vue-Architektur, Composition API und Nuxt-Performance

Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?

Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.

Architektur-Review

Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.

Performance-Audit

Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.

Nuxt-Integration

SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.

10. Zusammenfassung

VeeValidate + Zod: Das Wichtigste auf einen Blick

Kernidee

ein Zod-Schema liefert Validierungsregeln und TypeScript-Typ gleichzeitig

Verbindung

toTypedSchema() aus @vee-validate/zod als Brücke zwischen beiden

Fehleranzeige

errors-Objekt pro Feldname, refine() mit path für Cross-Field-Fehler

Nutzen

lohnt sich bei TypeScript-Projekten mit komplexeren oder wiederverwendeten Datenstrukturen

11. FAQ: VeeValidate + Zod: Das Wichtigste auf einen Blick

1Brauche ich Zod, um VeeValidate zu nutzen?
Nein, VeeValidate funktioniert eigenständig mit eingebauten Regeln. Zod ist eine optionale Ergänzung, die sich besonders bei TypeScript-Projekten mit komplexeren Formularen lohnt.
2Wie verbinde ich ein Zod-Schema mit VeeValidate?
Über die Funktion toTypedSchema() aus dem Paket @vee-validate/zod, die das Zod-Schema in das von useForm() erwartete validationSchema-Format umwandelt.
3Bleiben Validierungsregeln und TypeScript-Typ automatisch synchron?
Ja, weil der Typ über z.infer direkt aus dem Schema abgeleitet wird. Eine Änderung am Schema wirkt sich automatisch auf den abgeleiteten Typ aus.
4Wie validiere ich, dass zwei Felder übereinstimmen, etwa ein Passwort-Abgleich?
Über .refine() auf Objektebene im Zod-Schema, mit dem path-Parameter, der festlegt, welchem Feld der Fehler zugeordnet wird.
5Unterstützt Zod asynchrone Validierung, etwa eine Verfügbarkeitsprüfung?
Ja, über eine asynchrone Funktion innerhalb von .refine(). VeeValidate erkennt das automatisch und wartet auf das Promise, bevor es den Feldstatus aktualisiert.
6Wie zeige ich Fehlermeldungen im Template an?
Über das errors-Objekt aus useForm(), das für jeden Feldnamen die passende, von Zod erzeugte Meldung enthält, direkt im Template referenzierbar.
7Kann ich verschachtelte Formulare mit Zod validieren?
Ja, über z.object() für verschachtelte Objekte und z.array() für wiederholbare Strukturen, in Kombination mit VeeValidates useFieldArray() für dynamische Feldlisten.
8Lohnt sich die Kombination auch für kleine, einfache Formulare?
Für ein einzelnes Textfeld mit einer simplen Regel ist reines VeeValidate meist ausreichend und schneller umgesetzt, ohne den Schema-Aufwand.
9Kann das gleiche Zod-Schema auch für API-Validierung genutzt werden?
Ja, das ist einer der Hauptvorteile. Das gleiche Schema lässt sich sowohl im Formular als auch an jeder anderen Stelle nutzen, die denselben Datentyp erwartet.
10Muss der Feldname im Template exakt dem Schema-Schlüssel entsprechen?
Ja, VeeValidate ordnet Feldwerte über den Feldnamen dem passenden Schema-Schlüssel zu, eine Abweichung führt dazu, dass die Validierung für dieses Feld nicht greift.