Claude Code Zod: TypeScript-First Schema-Validierung, Type Inference und sichere APIs mit KI
Jede moderne TypeScript-Anwendung steht vor demselben Problem: Daten kommen von außen — aus HTTP-Requests, Environment-Variablen, JSON-Dateien, Formularen — und der Compiler kann diese Grenzen nicht absichern. Wer hier mit as SomeType oder ungeprüften JSON.parse-Aufrufen arbeitet, baut auf Sand. Zod schließt diese Lücke: Schemas definieren gleichzeitig Laufzeitvalidierung und TypeScript-Typen, ohne jede Eigenschaft doppelt zu schreiben. Claude Code versteht Zod-Patterns tief und kann komplette Validierungsschichten in Minuten aufbauen, die früher einen halben Arbeitstag gekostet haben.
Dieser Artikel zeigt, wie Claude Code Zod produktiv einsetzt — von primitiven Typen über diskriminierte Unions bis zu tRPC-Integration und React Hook Form. Alle Beispiele sind produktionsreif und direkt einsetzbar.
Warum Zod? Das Problem mit TypeScript-Grenzen
TypeScript-Typen existieren nur zur Compile-Zeit. Sobald eine API-Antwort, ein Formular-Submit oder eine Konfigurationsdatei ins Programm fließt, ist der Compiler blind. Das klassische Muster sieht so aus:
// Falsch: Compiler glaubt dir, Laufzeit nicht
const user = JSON.parse(response.data) as User;
console.log(user.email.toLowerCase()); // Crash, wenn email fehlt oder null ist
Zod löst das mit einem einzigen Konzept: Das Schema ist gleichzeitig der Typ. Kein Drift zwischen Typ-Definition und Validierungslogik möglich.
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().positive().optional(),
});
// Typ wird automatisch abgeleitet — kein separates interface nötig
type User = z.infer<typeof UserSchema>;
// Laufzeitvalidierung
const user = UserSchema.parse(JSON.parse(response.data));
// user ist jetzt sicher typisiert und validiert
Das Schlüsselkonzept: z.infer<typeof UserSchema> extrahiert den TypeScript-Typ aus dem Schema. Single Source of Truth.
Primitive Typen: z.string, z.number, z.boolean und mehr
Zod kennt alle JavaScript-Primitive und bietet pro Typ passende Validierungsmethoden. Claude Code schlägt bei der Eingabe direkt die verfügbaren Chains vor:
// Strings
const nameSchema = z.string()
.min(2, "Name muss mindestens 2 Zeichen haben")
.max(50, "Name darf maximal 50 Zeichen lang sein")
.trim()
.toLowerCase();
const emailSchema = z.string().email("Ungültige E-Mail-Adresse");
const urlSchema = z.string().url("Keine gültige URL");
const uuidSchema = z.string().uuid();
const dateStringSchema = z.string().datetime(); // ISO 8601
// Numbers
const priceSchema = z.number()
.positive("Preis muss positiv sein")
.multipleOf(0.01, "Maximal zwei Dezimalstellen");
const ageSchema = z.number().int().min(0).max(150);
// Booleans
const activeSchema = z.boolean();
// Dates (echtes Date-Objekt, nicht String)
const createdAtSchema = z.date();
// Enums — zwei Varianten
const RoleEnum = z.enum(["admin", "editor", "viewer"]);
type Role = z.infer<typeof RoleEnum>; // "admin" | "editor" | "viewer"
// Native TypeScript-Enums einbinden
enum Direction { Up = "UP", Down = "DOWN" }
const directionSchema = z.nativeEnum(Direction);
Objekte und Arrays: Das Herzstück der Validierung
Die meisten realen Daten sind verschachtelte Strukturen. z.object und z.array decken das vollständig ab:
const AddressSchema = z.object({
street: z.string().min(1),
city: z.string().min(1),
zip: z.string().regex(/^\d{5}$/, "5-stellige PLZ erwartet"),
country: z.string().length(2).toUpperCase(), // ISO 3166-1 alpha-2
});
const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
price: z.number().positive(),
tags: z.array(z.string()).min(1).max(10),
variants: z.array(z.object({
sku: z.string(),
stock: z.number().int().min(0),
})).optional(),
metadata: z.record(z.string(), z.unknown()), // beliebige Key-Value-Paare
});
type Product = z.infer<typeof ProductSchema>;
// Arrays mit Constraints
const NonEmptyStringArray = z.array(z.string()).nonempty();
const ExactlyThreeItems = z.tuple([z.string(), z.number(), z.boolean()]);
optional, nullable und default
Der Unterschied zwischen optional und nullable ist in TypeScript entscheidend:
// optional: Feld darf fehlen (undefined ist OK)
const schema1 = z.object({
nickname: z.string().optional(),
// Typ: { nickname?: string }
});
// nullable: Feld muss vorhanden sein, darf aber null sein
const schema2 = z.object({
deletedAt: z.date().nullable(),
// Typ: { deletedAt: Date | null }
});
// nullish: optional UND nullable
const schema3 = z.object({
bio: z.string().nullish(),
// Typ: { bio?: string | null | undefined }
});
// default: Standardwert bei fehlendem Feld
const schema4 = z.object({
role: z.enum(["user", "admin"]).default("user"),
isActive: z.boolean().default(true),
});
safeParse vs. parse: Fehlerbehandlung richtig machen
Das ist einer der wichtigsten Entscheidungspunkte beim Einsatz von Zod. parse wirft bei Fehler eine Exception, safeParse gibt ein Result-Objekt zurück:
// parse — wirft ZodError bei ungültigen Daten
try {
const user = UserSchema.parse(rawData);
// user ist garantiert valide
} catch (error) {
if (error instanceof z.ZodError) {
console.error(error.issues); // Array von Fehlern
}
}
// safeParse — kein try/catch nötig
const result = UserSchema.safeParse(rawData);
if (!result.success) {
// result.error ist ein ZodError
const errors = result.error.issues.map(issue => ({
path: issue.path.join("."),
message: issue.message,
}));
return { errors };
}
// result.data ist sicher typisiert
const user = result.data;
// Async-Variante für asynchrone Refinements
const asyncResult = await UserSchema.safeParseAsync(rawData);
ZodError strukturiert ausgeben
Zod-Fehler sind detailliert und für APIs gut nutzbar:
// flatten() — ideal für Formular-Fehler
const result = UserSchema.safeParse(badData);
if (!result.success) {
const flat = result.error.flatten();
/*
{
formErrors: [], // Fehler auf Root-Ebene
fieldErrors: {
email: ["Ungültige E-Mail-Adresse"],
name: ["Name muss mindestens 2 Zeichen haben"]
}
}
*/
}
// format() — verschachtelte Struktur
const formatted = result.error.format();
/*
{
_errors: [],
email: { _errors: ["Ungültige E-Mail-Adresse"] },
address: {
_errors: [],
zip: { _errors: ["5-stellige PLZ erwartet"] }
}
}
*/
Union, discriminatedUnion und z.enum
Für Daten, die mehrere Formen annehmen können, bietet Zod mächtige Union-Typen:
// z.union — testet alle Optionen
const StringOrNumber = z.union([z.string(), z.number()]);
// discriminatedUnion — schneller und mit besseren Fehlermeldungen
// wenn alle Varianten ein gemeinsames Diskriminierungsfeld haben
const PaymentSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("card"),
cardNumber: z.string().regex(/^\d{16}$/),
expiryMonth: z.number().int().min(1).max(12),
expiryYear: z.number().int().min(2026),
}),
z.object({
type: z.literal("sepa"),
iban: z.string().min(15).max(34),
bic: z.string().min(8).max(11),
}),
z.object({
type: z.literal("paypal"),
email: z.string().email(),
}),
]);
type Payment = z.infer<typeof PaymentSchema>;
// "type" narrowt automatisch auf die korrekte Variante
// Anwendung in einer Funktion
function processPayment(payment: Payment) {
switch (payment.type) {
case "card":
// payment.cardNumber ist hier verfügbar und typisiert
break;
case "sepa":
// payment.iban ist hier verfügbar
break;
}
}
Custom Refinements: Eigene Validierungslogik
Manchmal reichen die eingebauten Validierungen nicht. .refine() und .superRefine() ermöglichen beliebige Logik:
// refine — einfache Prüfung
const PasswordSchema = z.string()
.min(8, "Mindestens 8 Zeichen")
.refine(
(val) => /[A-Z]/.test(val),
"Mindestens ein Großbuchstabe erforderlich"
)
.refine(
(val) => /[0-9]/.test(val),
"Mindestens eine Ziffer erforderlich"
)
.refine(
(val) => /[^a-zA-Z0-9]/.test(val),
"Mindestens ein Sonderzeichen erforderlich"
);
// superRefine — mehrere Fehler gleichzeitig, mit Kontext
const RegistrationSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["confirmPassword"],
message: "Passwörter stimmen nicht überein",
});
}
});
// Asynchrone Refinements (z.B. Datenbankprüfungen)
const UniqueEmailSchema = z.string().email().refine(
async (email) => {
const existing = await db.user.findUnique({ where: { email } });
return !existing;
},
"Diese E-Mail-Adresse ist bereits registriert"
);
Transform und Preprocessing: Daten umformen
Zod kann nicht nur validieren, sondern Daten auch transformieren. Das ist besonders nützlich, wenn Eingabedaten in ein anderes Format gebracht werden müssen:
// transform — Daten nach der Validierung umformen
const DateStringSchema = z.string()
.datetime()
.transform((val) => new Date(val));
// Typ: string reinein, Date raus
const TrimmedString = z.string().transform((val) => val.trim());
const CommaSeparatedList = z.string()
.transform((val) => val.split(",").map((s) => s.trim()).filter(Boolean));
// Typ: string rein, string[] raus
// Komplexeres Beispiel: API-Response normalisieren
const ApiUserSchema = z.object({
first_name: z.string(),
last_name: z.string(),
email_address: z.string().email(),
}).transform((data) => ({
fullName: `${data.first_name} ${data.last_name}`,
email: data.email_address,
}));
// preprocess — Daten VOR der Validierung umformen
// Nützlich wenn Eingaben ein anderes Format haben als erwartet
const CoercedNumber = z.preprocess(
(val) => (typeof val === "string" ? parseFloat(val) : val),
z.number()
);
// "42.5" (String) wird zu 42.5 (Number) vor der Validierung
// Zod v3+ bietet z.coerce als Kurzform
const CoercedDate = z.coerce.date(); // "2026-09-11" → Date
const CoercedNumber2 = z.coerce.number(); // "42" → 42
const CoercedBoolean = z.coerce.boolean(); // "true" → true
Integration mit Express und Fastify
Zod ist der natürliche Partner für API-Validierung. Claude Code baut auf Anfrage komplette Middleware-Layers:
Express Middleware
import express from "express";
import { z } from "zod";
import { Request, Response, NextFunction } from "express";
// Generische Validierungs-Middleware
function validate<T extends z.ZodTypeAny>(schema: T) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: "Validierungsfehler",
details: result.error.flatten().fieldErrors,
});
}
req.body = result.data; // transformierte Daten
next();
};
}
// Schema definieren
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["user", "admin"]).default("user"),
});
// Route mit Validierung
const app = express();
app.use(express.json());
app.post(
"/api/users",
validate(CreateUserSchema),
async (req, res) => {
// req.body ist jetzt sicher typisiert als z.infer<typeof CreateUserSchema>
const user = await createUser(req.body);
res.status(201).json(user);
}
);
// Query-Parameter validieren
const ListUsersQuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
role: z.enum(["user", "admin"]).optional(),
});
app.get("/api/users", (req, res) => {
const result = ListUsersQuerySchema.safeParse(req.query);
if (!result.success) {
return res.status(400).json({ error: result.error.flatten() });
}
const { page, limit, role } = result.data;
// ...
});
Fastify mit Zod-Integration
import Fastify from "fastify";
import { z } from "zod";
const fastify = Fastify();
// Fastify akzeptiert JSON Schema — Zod kann dazu konvertiert werden
// mit der Library zod-to-json-schema
import zodToJsonSchema from "zod-to-json-schema";
const BodySchema = z.object({
title: z.string().min(1),
content: z.string().min(10),
published: z.boolean().default(false),
});
fastify.post("/posts", {
schema: {
body: zodToJsonSchema(BodySchema),
},
handler: async (request) => {
const body = BodySchema.parse(request.body);
// body ist sicher typisiert
return { id: crypto.randomUUID(), ...body };
},
});
tRPC + Zod: End-to-End-Typsicherheit
tRPC und Zod sind füreinander gemacht. Das Kombination liefert vollständige Typsicherheit vom Datenbankschema bis zur React-Komponente, ohne einen einzigen API-Vertrag manuell zu pflegen:
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
const UserRouter = t.router({
// Query mit validierter Eingabe
getById: t.procedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input }) => {
// input.id ist string (UUID)
const user = await db.user.findUnique({ where: { id: input.id } });
if (!user) throw new TRPCError({ code: "NOT_FOUND" });
return user;
}),
// Mutation mit komplexem Schema
create: t.procedure
.input(z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string().regex(/^\d{5}$/),
}).optional(),
}))
.mutation(async ({ input }) => {
return await db.user.create({ data: input });
}),
// Subscription (z.B. für Live-Updates)
onUserCreated: t.procedure
.input(z.object({ organizationId: z.string() }))
.subscription(({ input }) => {
return observable<User>((emit) => {
const unsub = userCreatedEmitter.on(input.organizationId, emit.next);
return () => unsub();
});
}),
});
// Im Client: vollständige Typsicherheit ohne Codegen
const { data } = trpc.user.getById.useQuery({ id: "abc-123" });
// data ist User | undefined, komplett typisiert
Zod und tRPC in der Praxis meistern
Im Kurs baust du komplette typsichere APIs mit Claude Code — von der Datenbankschicht bis zur React-UI. Kein Raten, keine Lücken in der Typkette.
Kurs starten — ab €29 Pro-Version ansehenReact Hook Form + Zod: Formular-Validierung
Die Kombination aus React Hook Form und Zod ist der Standard für typsichere Formulare in React. Der zodResolver verbindet beide Welten:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
// Schema definiert Formular-Struktur UND Validierung
const CheckoutSchema = z.object({
firstName: z.string().min(1, "Vorname erforderlich"),
lastName: z.string().min(1, "Nachname erforderlich"),
email: z.string().email("Gültige E-Mail eingeben"),
phone: z.string()
.regex(/^[+]?[\d\s\-()]{8,20}$/, "Ungültige Telefonnummer")
.optional(),
address: z.object({
street: z.string().min(1, "Straße erforderlich"),
city: z.string().min(1, "Stadt erforderlich"),
zip: z.string().regex(/^\d{5}$/, "5-stellige PLZ erforderlich"),
}),
paymentMethod: z.enum(["card", "sepa", "paypal"]),
newsletter: z.boolean().default(false),
});
type CheckoutFormData = z.infer<typeof CheckoutSchema>;
function CheckoutForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
watch,
} = useForm<CheckoutFormData>({
resolver: zodResolver(CheckoutSchema),
defaultValues: {
paymentMethod: "card",
newsletter: false,
},
});
const paymentMethod = watch("paymentMethod");
const onSubmit = async (data: CheckoutFormData) => {
// data ist vollständig typisiert und validiert
await submitOrder(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} placeholder="Vorname" />
{errors.firstName && (
<span className="error">{errors.firstName.message}</span>
)}
<input {...register("email")} type="email" placeholder="E-Mail" />
{errors.email && (
<span className="error">{errors.email.message}</span>
)}
{/* Conditional Fields basierend auf paymentMethod */}
{paymentMethod === "card" && (
<input {...register("cardNumber")} placeholder="Kartennummer" />
)}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Wird verarbeitet..." : "Bestellung aufgeben"}
</button>
</form>
);
}
Environment-Variablen validieren
Ein unterschätzter Anwendungsfall für Zod ist die Validierung von Environment-Variablen beim App-Start. So scheitert die Anwendung sofort mit einer klaren Fehlermeldung, wenn eine kritische Variable fehlt:
// env.ts — einmal importieren, überall nutzen
import { z } from "zod";
const EnvSchema = z.object({
// Server
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
PORT: z.coerce.number().int().positive().default(3000),
// Datenbank
DATABASE_URL: z.string().url("DATABASE_URL muss eine gültige URL sein"),
DATABASE_POOL_SIZE: z.coerce.number().int().min(1).max(50).default(10),
// Auth
JWT_SECRET: z.string().min(32, "JWT_SECRET muss mindestens 32 Zeichen lang sein"),
JWT_EXPIRES_IN: z.string().default("7d"),
// Externe Dienste
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
SENDGRID_API_KEY: z.string().startsWith("SG.").optional(),
// Feature Flags
ENABLE_REGISTRATION: z.coerce.boolean().default(true),
});
// Einmalig beim Start validieren
const parseResult = EnvSchema.safeParse(process.env);
if (!parseResult.success) {
console.error("Ungültige Environment-Variablen:");
console.error(parseResult.error.flatten().fieldErrors);
process.exit(1);
}
export const env = parseResult.data;
// env.DATABASE_URL ist jetzt string, nicht string | undefined
Testing von Zod-Schemas
Schemas sollten getestet werden — sowohl für valide als auch für ungültige Inputs. Claude Code generiert vollständige Test-Suites auf Anfrage:
import { describe, it, expect } from "vitest";
import { UserSchema, CreateUserSchema } from "./schemas";
describe("UserSchema", () => {
// Valide Daten
it("akzeptiert gültige Benutzerdaten", () => {
const validUser = {
id: "550e8400-e29b-41d4-a716-446655440000",
email: "max@beispiel.de",
name: "Max Mustermann",
};
const result = UserSchema.safeParse(validUser);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.email).toBe("max@beispiel.de");
}
});
// Ungültige E-Mail
it("lehnt ungültige E-Mail ab", () => {
const result = UserSchema.safeParse({
id: "550e8400-e29b-41d4-a716-446655440000",
email: "keine-email",
name: "Test",
});
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.flatten().fieldErrors.email).toBeDefined();
}
});
// Edge Cases
it("lehnt leeren Namen ab", () => {
const result = UserSchema.safeParse({
id: "550e8400-e29b-41d4-a716-446655440000",
email: "test@test.de",
name: "",
});
expect(result.success).toBe(false);
});
// Transform prüfen
it("konvertiert Datum-Strings zu Date-Objekten", () => {
const EventSchema = z.object({
startsAt: z.coerce.date(),
});
const result = EventSchema.parse({ startsAt: "2026-09-11T10:00:00Z" });
expect(result.startsAt).toBeInstanceOf(Date);
});
});
Fortgeschrittene Patterns: Schema-Komposition und Wiederverwendung
Gut strukturierte Zod-Schemas sind modular und wiederverwendbar. Claude Code schlägt dabei die besten Kompositionsmuster vor:
// Basis-Schemas für Wiederverwendung
const IdSchema = z.string().uuid();
const TimestampsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
});
// Kombinieren mit .merge() und .extend()
const BaseEntitySchema = z.object({ id: IdSchema }).merge(TimestampsSchema);
const UserSchema = BaseEntitySchema.extend({
email: z.string().email(),
name: z.string(),
});
// Partielle Schemas für Updates
const UpdateUserSchema = UserSchema
.omit({ id: true, createdAt: true, updatedAt: true })
.partial(); // alle Felder optional
// Picking für spezifische Felder
const UserPublicSchema = UserSchema.pick({ id: true, name: true });
// Schema-Hierarchie für API-Varianten
const CreateProductSchema = z.object({
name: z.string().min(1),
price: z.number().positive(),
categoryId: z.string().uuid(),
});
const UpdateProductSchema = CreateProductSchema.partial();
const ProductResponseSchema = CreateProductSchema.extend({
id: z.string().uuid(),
slug: z.string(),
createdAt: z.date(),
category: z.object({ id: z.string(), name: z.string() }),
});
Claude Code und Zod: Der Workflow in der Praxis
Der größte Vorteil von Claude Code Zod ist die Geschwindigkeit, mit der komplette Validierungsschichten entstehen. Ein typischer Workflow:
Du beschreibst deiner Claude Code-Instanz die Datenstruktur auf natürliche Weise: "Ich brauche ein Schema für eine Bestellungsanlage: Kunde mit Adresse, mehrere Positionen mit Produkt-ID und Menge, Zahlungsart muss card oder rechnung sein, Gesamtbetrag wird berechnet."
Claude Code erstellt daraus:
- Das vollständige Zod-Schema mit allen Validierungsregeln
- Die abgeleiteten TypeScript-Typen per
z.infer - Die passende API-Middleware für Express oder Fastify
- Vitest-Tests für die wichtigsten Valid- und Invalidfälle
- Fehlerbehandlung mit strukturierten Fehlermeldungen
Was früher einen halben Tag dauerte — Schema entwerfen, Typen schreiben, Validierung implementieren, Tests schreiben — passiert in wenigen Minuten. Die Qualität ist dabei höher als handgeschriebener Code, weil Zod-Patterns konsistent angewandt werden.
Schema-Validierung ist kein Boilerplate mehr, sondern Präzision: mit Claude Code schreibst du ein mal, was deine Daten bedeuten, und bekommst Laufzeitsicherheit und TypeScript-Typen gratis dazu.
Häufige Fehler und wie man sie vermeidet
1. parse statt safeParse in Produktionscode: parse wirft Exceptions, die unbehandelt den Server zum Absturz bringen. Nutze safeParse an API-Grenzen und gib saubere HTTP-Fehler zurück.
2. Schema nicht wiederverwendet: Wer für Create, Update und Response drei separate Interfaces schreibt, pflegt dreifach. Starte mit einem Basis-Schema und nutze .extend(), .omit(), .partial().
3. z.any() als Ausweg: z.any() schaltet Typsicherheit ab. Besser: z.unknown() mit expliziter Behandlung, oder das Schema vollständig definieren.
4. Fehlermeldungen auf Englisch lassen: Zod liefert englische Standardmeldungen. Für deutschsprachige Apps alle message-Parameter setzen oder einen globalen errorMap konfigurieren:
import { z, ZodIssueCode } from "zod";
z.setErrorMap((issue, ctx) => {
if (issue.code === ZodIssueCode.too_small && issue.type === "string") {
return { message: `Mindestens ${issue.minimum} Zeichen erforderlich` };
}
if (issue.code === ZodIssueCode.invalid_string && issue.validation === "email") {
return { message: "Bitte gib eine gültige E-Mail-Adresse ein" };
}
return { message: ctx.defaultError };
});
Zod hat sich als Standard-Validierungsbibliothek im TypeScript-Ökosystem durchgesetzt — nicht zufällig, sondern weil das Konzept stimmt: Ein Schema, eine Wahrheit, null Drift zwischen Typen und Laufzeit. Claude Code Zod macht diesen Ansatz noch mächtiger: Was früher präzises Handwerk war, wird mit KI-Unterstützung zum schnellen, konsistenten Prozess. Das Ergebnis sind sicherere APIs, weniger Runtime-Fehler und TypeScript-Typen, die tatsächlich das widerspiegeln, was im System passiert.
Der nächste Schritt: Diese Patterns in einem echten Projekt anwenden und die Zeit messen, die du mit sauberem Schema-Design sparst.
Typsichere Apps mit Claude Code bauen
Im Kurs lernst du Zod, tRPC und React Hook Form im Zusammenspiel — von der ersten Zeile bis zur produktionsfertigen API. Mit echten Projekten, nicht Spielzeug-Beispielen.
Basis-Kurs — €29 Pro-Version — €49