On this Page
On this Guide
- Lesson 13: Validating a User Registration Form
- Lesson 14: Validating API Query Parameters
- Lesson 15: Validating Environment Variables
- Lesson 16: Handling Nested Errors and Formatted Output
- Lesson 17: Reusable Zod Error Parser for Forms
- Lesson 18: Blog Post Editor – Real-World Case Study
- Lesson 19: Zod + React Hook Form Integration
- Lesson 21: Validating Express Requests with Zod
- Lesson 22: Custom Zod Error Formatter for Express APIs
- Lesson 20: Final Thoughts, Best Practices, and Resources
The Problem
We want to validate a user registration form with these fields:
- Name (at least 2 characters)
- Email (must be valid)
- Password (at least 6 characters, one uppercase)
- Confirm password (must match password)
We want to:
- Show helpful error messages
- Validate on both frontend and backend
- Avoid code duplication
Zod Schema for Registration
import { z } from "zod";
export const RegisterSchema = z
.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
password: z
.string()
.min(6, "Password must be at least 6 characters")
.refine((val) => /[A-Z]/.test(val), {
message: "Password must contain at least one uppercase letter",
}),
confirmPassword: z.string(),
})
.superRefine(({ password, confirmPassword }, ctx) => {
if (password !== confirmPassword) {
ctx.addIssue({
path: ["confirmPassword"],
code: "custom",
message: "Passwords do not match",
});
}
});
Validating Password Confirmation
The .superRefine() method is essential for comparing fields. It allows cross-field logic and precise error placement.
// ❌ Will add error only to confirmPassword
ctx.addIssue({
path: ["confirmPassword"],
code: "custom",
message: "Passwords do not match",
});
Handling and Displaying Errors
In React (e.g. with React Hook Form):
const result = RegisterSchema.safeParse(formValues);
if (!result.success) {
const errors = result.error.format();
console.log(errors.email?._errors); // ["Invalid email address"]
}
In Backend (e.g. Node/Express):
const body = await request.json();
const result = RegisterSchema.safeParse(body);
if (!result.success) {
return Response.json({ errors: result.error.flatten() }, { status: 400 });
}
Summary
- Zod makes complex form validation clean, readable, and reusable
- Use
.refine()for field-specific logic, and.superRefine()for cross-field validation .safeParse()gives structured errors for frontend rendering or backend responses
Up next: Lesson 14 – Validating API Query Parameters
Master the Code, Be the Guru!