Kontakt
Engineering

Typsichere Formulare ohne Tränen

Ein Schema, drei Abnehmer, und keine Stelle, an der ein Feldname eine Zeichenkette ist, die der Compiler nie gesehen hat.

Von Ovadev2 Min. Lesezeit

Alle Beiträge
  • typescript
  • Formulare
  • Validierung
WhatsAppLinkedInX

Formulare verrotten auf eine bestimmte Art: Das name-Attribut des Eingabefelds, die Prüfregel, das Destrukturieren auf dem Server und die Spalte sagen alle dasselbe Wort, und nichts in der Toolchain weiss, dass es dasselbe Wort ist. Benennen Sie eines um, und die anderen drei finden Sie in der Produktion.

Ein Schema, überall abgeleitet#

Das Muster ist nicht raffiniert. Es gibt genau eine Stelle, an der ein Feld deklariert wird, und alles andere leitet sich davon ab, die name-Attribute eingeschlossen. Genau diesen Schritt lassen die meisten Codebasen aus.

ts
export const contactSchema = {
  name:    { label: "Name",      required: true,  max: 80 },
  email:   { label: "E-Mail",    required: true,  email: true },
  message: { label: "Nachricht", required: true,  max: 4000 },
} as const;

export type ContactField = keyof typeof contactSchema;
export type ContactValues = Record<ContactField, string>;
export type ContactErrors = Partial<Record<ContactField, string>>;

as const ist das, was es funktionieren lässt: Aus den Schlüsseln wird eine Union aus Literalen statt string, und damit weist alles, was ein ContactField entgegennimmt, einen Tippfehler schon beim Kompilieren zurück.

tsx
// dem Feld lässt sich kein Name geben, der nicht im Schema steht
function Field({ name }: { name: ContactField }) {
  const f = contactSchema[name];
  return <input name={name} required={f.required} maxLength={f.max} />;
}

Einmal prüfen, in einer Funktion, die beide Seiten importieren#

Prüfung im Client ist eine Höflichkeit; Prüfung auf dem Server ist die eigentliche Regel. Der Fehler ist, sie zweimal zu schreiben. Eine reine Funktion, importiert vom Formular für die sofortige Rückmeldung und von der Server Action für die Entscheidung, die zählt.

ts
export function validate(values: ContactValues): ContactErrors {
  const errors: ContactErrors = {};
  for (const key of Object.keys(contactSchema) as ContactField[]) {
    const rule = contactSchema[key];
    const value = values[key]?.trim() ?? "";
    if (rule.required && !value) errors[key] = `${rule.label} fehlt`;
    else if ("email" in rule && value && !value.includes("@")) errors[key] = "Das sieht nicht nach einer Adresse aus";
    else if (rule.max && value.length > rule.max) errors[key] = `${rule.label} ist zu lang`;
  }
  return errors;
}

Fehler, die den Rückweg überleben#

Die Fehler sind nach Feldnamen abgelegt, damit die Antwort des Servers ohne Nachschlagetabelle direkt auf die Eingabefelder zurückfällt, und damit ein Feld, das eine neue Regel bekommt, nicht vergessen kann, sie anzuzeigen.

Was wir mit Absicht nicht tun#

  • Keine Formularbibliothek. Drei davon im Jahr rechtfertigen keine Abhängigkeit mit eigenem Denkmodell.
  • Keine Prüfung nur im Client. Der Client bekommt dieselbe Funktion, aber der Server entscheidet.
  • Kein Schema in der Datenbankschicht. Die Spaltenzuordnung leitet sich aus denselben Schlüsseln ab; die Datenbank importiert keinen Formularcode.
Ovadev

Ein Softwareunternehmen in Baar, Zug. Wir bauen eigene Produkte, betreiben sie danach selbst und schreiben auf, was uns das beibringt.

Kontakt aufnehmen
Weiterlesen

Mehr aus dem Studio: zuerst Engineering, dann der Rest.

Alle Beiträge
© 2026 Ovadev GmbHAus der Schweiz