Build a Typed REST API with Express, TypeScript & Zod

Lesson 3 of 8

Defining Schemas with Zod & Inferring Types

With the project running, model the actual data the API will accept and return, starting from Zod schemas instead of hand-written interfaces.

TypeScript

Each field reads like a description of the constraint, z.string().min(1).max(200) isn't just "a string," it's "a string between 1 and 200 characters," checked for real when .parse() runs, not just documented in a comment.

Deriving request schemas from the base schema

TypeScript

.omit() and .partial() on a Zod schema work exactly like Omit<T, K> and Partial<T> from the intermediate TypeScript course, except they reshape the runtime validator, not just the compile-time type, and z.infer immediately gives you the matching TypeScript type for free.

Custom validation with refine

TypeScript

.refine() adds a check .min()/.max()/.enum() can't express on their own, an arbitrary function returning true/false, with a custom error message attached to a specific field.

NOTE

Notice there's still only one place Task's shape is written down, the TaskSchema. CreateTaskInput, UpdateTaskInput, and the runtime checks for all three all derive from it, change one field once and everything downstream stays in sync automatically.

📝 Zod Schemas Quiz

Passing score: 70%
  1. 1.What does TaskSchema.omit({ id: true, done: true }) produce?

  2. 2..refine() lets you add a custom validation rule beyond what built-in checks like .min() express.

  3. 3.TaskSchema.____() (with no arguments) makes every field of the schema optional, matching TypeScript's Partial.

  4. 4.What does .refine() add that .min()/.max()/.enum() cannot express alone?

  5. 5.z.infer<typeof TaskSchema> has to be kept manually in sync with TaskSchema by hand.