A castle-like building behind a half-open gate
Webentwicklung |

User Input – klingt harmlos, ist es aber nicht

leo.jpg.png

Leonhard

15. Juli 2024

Hinweis: Dieser Artikel wurde unter Zuhilfenahme von KI aus dem Englischen übersetzt. Hier geht's zum Originaltext.

In den letzten Jahren hat sich eine Bibliothek in unseren Codebasen eingeschlichen – zum Glück! Sie heißt zod und hat sowohl Mehrwert als auch Entwicklerfreude in unsere Projekte gebracht. Das Projekt beschreibt sich selbst als eine Bibliothek für "TypeScript-first Schema-Validierung mit statischer Typenableitung".

In diesem Beitrag werden wir ergründen, warum und wie wir zod im Kontext einer Full-Stack-Webanwendung mit TypeScript nutzen können.

Warum validieren?

Alle Benutzereingaben sollten als potenziell gefährlich betrachtet werden. Es gibt zwei Hauptaspekte zu beachten:

  • Ungültige Eingaben: Deine Geschäftsanforderungen verlangen, dass Daten einer bestimmten Form entsprechen. Zum Beispiel sollte das Feld "E-Mail" im Registrierungsformular tatsächlich eine syntaktisch gültige E-Mail-Adresse enthalten. Ungültige Daten können zu einer defekten Anwendung oder schlicht zu nutzlosen Ergebnissen führen.
  • Böswillige Eingaben: Du hast sicherlich schon von Cross-Site-Scripting (XSS) oder SQL-Injection-Angriffen gehört – beide immer noch in den Top 10 der Web-Anwendungs-Schwachstellen. Ebenso möchtest du zusätzliche Felder aus einer Payload entfernen, bevor du in die Datenbank schreibst – sonst könnte jemand einen Fremdschlüssel wie owner_id überschreiben, um den Datensatz böswillig mit seinem eigenen Benutzer zu verknüpfen. Eine Möglichkeit, diese Angriffe zu bekämpfen, besteht darin, die eingehenden Daten zu validieren.

Diese Gründe sind sehr produktspezifisch. Wie passt also die Entwicklerfreude hierein? Als Entwickler arbeiten wir gerne mit typisierten Daten. Es macht unser Leben einfacher, indem es Autovervollständigung, Typenprüfung und Refactoring-Tools bietet. Die Typen separat von der Geschäftslogik zu definieren, kann jedoch mühsam sein – wie hält man diese synchron? Hier kann uns die Validierung von Daten (oder besser gesagt das Parsen, wie wir noch sehen werden) ebenfalls helfen. Nachdem die Daten geparst wurden, können wir sicher sein, dass sie die Form haben, die wir erwarten.

Puh, das war ziemlich technisch. Wie können wir uns das bildlich vorstellen?

Stell dir das Parsen wie die Kontrolle von Besuchern am Tor einer Burg vor. Bevor sie das Tor passieren, sind die Besucher unbekannt. Wir müssen sie überprüfen. Wenn sie dem Schema entsprechen (freundliche Händler, Bewohner, ...), dürfen sie eintreten. Darüber hinaus können wir ihnen eine bestimmte Rolle zuweisen, die sie von nun an mit sich tragen. Diese Rolle ist während ihrer gesamten Zeit in der Burg (unserer Anwendung) bekannt. Bei der Überprüfung nehmen wir ihnen möglicherweise gefährliche Gegenstände ab. Wenn sie jedoch nicht dem Schema entsprechen (Feinde, Spione, ...), können wir ihnen den Zutritt verweigern und sie throwen (hinauswerfen).

Eine Burg, die durch ein Schema am Tor gesichert ist.

Genug der Theorie. Wie funktioniert das mit zod?

Grundlegende Verwendung

Betrachten wir ein einfaches Schema für eine Person, bestehend aus Name und Alter:

So weit, so einfach!

Aber Moment... warum heißt die Methode zur Validierung parse()? Das liegt daran, dass es bei zod nicht nur um die Validierung von Daten geht, sondern auch um das Parsen. Tatsächlich folgt zod der Idee des Parsens statt Validierens. Das bedeutet, dass es versucht, die Daten gemäß dem definierten Schema zu parsen. Wenn die Daten nicht dem Schema entsprechen, wirft parse() einen Fehler. Nachdem zod ein generisches Objekt geparst hat, erhält es einen spezifischen TypeScript-Typ, den es von nun an trägt – perfekt für die weitere Verarbeitung!

Dies steht im Gegensatz zu if-Anweisungen, die über den gesamten Codebase verstreut sind, um den Wert und die Form eines Objekts zu überprüfen. Diese if-Anweisungen lassen sich auch nicht einfach komponieren. Vergleiche das mit dem obigen Beispiel, wo wir die Methoden string() und min() verketteten oder das personSchema aus dem legalAgeSchema komponierten. Dies ist ein leistungsstarkes Konzept, das verwendet werden kann, um komplexe Schemas aus einfachen Bausteinen zu erstellen.

Full-Stack-Anwendung

In einer Full-Stack-Anwendung kannst du zod sowohl im Frontend als auch im Backend verwenden. Normalerweise ist es sinnvoll, das Schema in einem gemeinsam genutzten Paket im Monorepo zu definieren.

Das Frontend würde das Schema verwenden, um die Formularwerte zu parsen, bevor es sie an das Backend sendet. Auf diese Weise kann das Frontend dem Benutzer sofortiges und handlungsorientiertes Feedback durch Fehlermeldungen oder Toasts geben – eine angenehme Benutzererfahrung.

Das Backend würde den eingehenden Request-Body parsen. Mit Hilfsmethoden wie extend() kann das Backend dem gemeinsamen Schema zusätzliche Felder hinzufügen. Zum Beispiel könnte das Backend den Daten ein generiertes id-Feld hinzufügen oder die user_id aus dem Autorisierungskontext extrahieren.

Das Parsen sollte am Tor unserer Burg, der Systemgrenze, so früh wie möglich erfolgen. Der gesamte weitere Code kann sich dann auf die Typeninformationen verlassen – und bringt uns einen Schritt näher an vollständige Full-Stack-Typensicherheit.

Beispiel: Suche

Betrachten wir eine Anwendung für die Suche nach einem JavaScript-Paket. Der Benutzer könnte eine Suchanfrage eingeben, die zu durchsuchenden Registrierungen auswählen und die Anzahl der Ergebnisse pro Seite festlegen:

Die Formular-UI unseres Paketsuche-Beispiels, bestehend aus einem Namensfeld, zwei Checkboxen für Registrierungen und einem Dropdown für die Anzahl der Ergebnisse pro Seite.

Mit zod können wir ein Schema definieren, das die Form des erwarteten Objekts beschreibt.

Man könnte sagen, dass das Schema als Vertrag zwischen Frontend und Backend fungiert. Wenn sich beide Seiten an denselben Vertrag halten, werden beide Teile zusammenarbeiten. Ein glückliches Leben in der Burg!

Geparst, abgeleitet, geliefert

Wir haben kaum an der Oberfläche gekratzt. Es gibt andere Arten von Benutzereingaben, die du berücksichtigen solltest, wie URL-Suchparameter oder Webhook-Payloads. Es gibt auch von zod inspirierte Bibliotheken, die Schemas in den Mittelpunkt stellen. Bei tRPC kannst du beispielsweise Eingabe- (für Request) und Ausgabe- (für Response)-Schemas definieren, um vollständig typsichere Anwendungen zu schreiben. Manchmal benötigst du möglicherweise asynchrone Validierung. Zum Beispiel möchtest du vielleicht die E-Mail-Adresse eines Benutzers überprüfen, indem du prüfst, ob sie bereits verwendet wird – eine asynchrone Datenbankabfrage. zod ermöglicht dies mit refine() und parseAsync().

Es gibt viele ähnliche Bibliotheken zu zod. Es war einfach eine der ersten mit großartiger TypeScript-Unterstützung und hat seitdem eine große Anhängerschaft gewonnen. Eine interessante Alternative ist valibot, das darauf abzielt, die Bundle-Größe zu reduzieren, indem es deinem Bundler ermöglicht, die verwendeten Bibliotheksfunktionen effektiv zu tree-shaken.

Letztendlich spielt es keine Rolle, wie du validierst. Was zählt, ist, dass du dich um die Datenvalidierung (oder besser gesagt das Parsen) in deiner Anwendung kümmerst. Sowohl für den Mehrwert des Projekts als auch für die Entwicklerfreude.

🤖 Hinweis zur Verwendung von KI in diesem Artikel: Dieser Artikel wurde von Menschen geschrieben (danke für das Feedback, Irena & Johannes!), einschließlich Titel, Konzepte und Codebeispiele. Allerdings haben wir KI genutzt, um den Schreibstil zu verbessern.

Header-Foto von Ümit Yıldırım auf Unsplash

TypeScript

Web App Development

Best Practices

Full-Stack

Validation

Weitere Themen

Lea, 04.04.2025

Vue clever nutzen – Wiederverwendbarkeit für Einsteiger:innen

Vue

JavaScript

Reusability

DRY Principle

Components

Composables

Zum Blogartikel
Header-top-ten-mistakes-to-avoid.png

Francesca, Ricarda, 02.04.2025

Die 10 häufigsten Fehler bei der Entwicklung digitaler Produkte – und wie du sie vermeidest

MVP development

UX/UI design

product vision

agile process

user engagement

product development

Zum Blogartikel

Judith, 31.03.2025

Von der Hypothese zur echten Erkenntnis: MVPs richtig einsetzen

Zum Blogartikel