On this Page
On this Guide
- Lesson 06: Schema Composition with Merge, Extend, Pick, and Omit
- Lesson 07: Refinement, superRefine, and Custom Validators
- Lesson 08: Discriminated Unions and Tagged Types
- Lesson 09: Transforming and Preprocessing Data
- Lesson 10: Working with Records, Maps, Sets, and Enums
- Lesson 11: Async Validation with Promises
- Lesson 12: Reusable Schemas Across Frontend and Backend
Why Share Schemas?
Imagine this:
- Your backend validates request bodies before saving to the database
- Your frontend validates user input before sending requests
- Both use the same logic β but youβve written it twice π°
With Zod, you can define a schema once, then reuse it across:
- API endpoints
- Forms and inputs
- TypeScript types
- Testing
> One schema to rule them all.
Project Structure for Shared Schemas
In a monorepo or fullstack project, organize your shared logic like this:
/packages
/schemas
user.ts
/frontend
/backend
Or in a single repo:
/src
/schemas
user.ts
/frontend
/backend
Your schema files become single sources of truth.
Defining Schemas Once, Using Everywhere
Letβs define a reusable user schema:
// src/schemas/user.ts
import { z } from "zod";
export const RegisterSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
password: z.string().min(6),
});
export type RegisterInput = z.infer<typeof registerschema>;
Then in your backend route:
// backend/api/register.ts
import { RegisterSchema } from "@/schemas/user";
const body = await req.json();
const result = RegisterSchema.safeParse(body);
And in your frontend form:
// frontend/forms/RegisterForm.ts
import { RegisterSchema } from "@/schemas/user";
RegisterSchema.parse(formData); // Client-side check
Example: User Registration
Shared schema file:
// schemas/user.ts
export const UserLoginSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
export type UserLoginData = z.infer<typeof userloginschema>;
Backend:
const data = await req.json();
const result = UserLoginSchema.safeParse(data);
Frontend:
const errors = validateForm(formState, UserLoginSchema);
Now both sides stay in sync β no drift, no surprises.
Tips for Schema Versioning
- β Tag your schemas with comments (or filenames) when API versions change
- β Avoid breaking schema changes without tracking them in version control
- β
Use
extend(),pick(), andomit()to adapt base schemas safely
Summary
- Use Zod to centralize validation logic and types
- Structure your project to share schemas across layers
- Infer types from schemas instead of duplicating interfaces
- Simplify testing, reduce bugs, and save time
Youβve reached the end of the Advanced Guide to Zod!
Up next: the final section β Practical Examples, Problems, and Real-World Solutions.
Master the Code, Be the Guru!