For this lesson, we’ll organise our user-related schemas and validation middleware functions in a file called middleware/user-validation.ts. This keeps everything related to user validation in one place.

This is far from the only way to organise the code, and you could, for example, put the schemas in a schemas/ folder, but we’ll use the simpler approach for this course.

Create middleware/user-validation.ts and import Zod:

import { z } from "zod";

Building Zod Schemas

Let’s start by replacing the manual validation with Zod schemas.

Creating the Required User Data Schema

Remember the manual validation we had in previous lessons for checking that the request had both username and email properties:

const { username, email } = req.body;
if (!username || !email) {
  return res.status(400).json({ error: "Username and email are required" });
}

Here’s how to define a much better validation with Zod:

const requiredUserDataSchema = z.object({
  username: z
    .string()
    .min(2, "Username must be at least 2 characters")
    .max(50, "Username must not exceed 50 characters"),
  email: z.email("Email must be a valid email"),
});

What’s happening here

  • z.object({}): Defines an object schema with a property for each value we expect and want to validate.
  • We expect two values, username and email.
  • By default in Zod, properties are required, so username and email are required.
  • username must be a string (validated with z.string()), must be a minimum of 2 characters (validated with .min()), and must not exceed 50 characters (validated with .max()). We are providing custom error messages, too.
  • email must be a valid email format (validated with .email()). We are providing a custom error message too.

Note that although we provide custom error messages, Zod will give you sensible default messages if you don’t specify them.

Schema for ID Validation

Remember the manual ID validation we had in previous lessons:

// Manual validation (what we're replacing)
const userId = Number(req.params.id);
if (isNaN(userId)) {
  return res.status(400).json({ error: "Invalid user ID" });
}

Here’s how to define much better ID validation with Zod:

const userIdSchema = z.object({
  id: z.string().regex(/^\d+$/, "ID must be a positive number"),
});

What this does:

  • z.string(): URL parameters are always strings.
  • .regex(/^\d+$/, "..."): Uses a regular expression pattern that ensures the string contains only digits (no letters, negative signs, or decimals).
  • Custom error message: Explains what’s wrong.

Schema for Partial Updates

So far, all fields in our schemas have been required (this is Zod’s default behaviour). For PATCH requests allowing partial updates, we need some fields to be optional.

Here’s how to create a schema for partial user updates using .optional():

const partialUserDataSchema = z.object({
  username: z
    .string()
    .min(2, "Username must be at least 2 characters")
    .max(50, "Username must not exceed 50 characters")
    .optional(),
  email: z.email("Email must be a valid email").optional(),
});

What .optional() does

  • Fields can be omitted from the request entirely.
  • If provided, they must still meet the validation rules.
  • Perfect for PATCH requests where users can update just some fields.

Add these three schemas to middleware/user-validation.ts.



Repo link

Tags: