Why Type Safety Matters
When building APIs, the gap between backend and frontend types is where most bugs live. You change a field on the server, forget to update the client, and suddenly you have a runtime error that only shows up in production.
Zod solves this by giving you a single source of truth that validates data at runtime and infers TypeScript types at compile time.
The Pattern
The core idea is simple: define your validation schemas once, derive types from them, and reuse those schemas across your entire stack.
import { z } from "zod";
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"]),
});
type User = z.infer<typeof UserSchema>;
Now User is fully typed, and any data coming through your API gets validated against the exact same shape.
Shared Schemas
I keep schemas in a lib/schemas directory and import them from both server and client code:
lib/
schemas/
user.ts
project.ts
ai-file.ts
When a Next.js route handler receives a request body, it validates with the same schema the frontend uses for form validation:
// app/api/users/route.ts
import { UserSchema } from "@/lib/schemas/user";
export async function POST(request: Request) {
const body = await request.json();
const result = UserSchema.safeParse(body);
if (!result.success) {
return Response.json(result.error, { status: 400 });
}
// result.data is fully typed
}
Form Validation on the Client
The same schema can power your form validation without any extra work:
const form = useForm({
schema: UserSchema,
onSubmit: async (values) => {
// values is already typed as User
await fetch("/api/users", {
method: "POST",
body: JSON.stringify(values),
});
},
});
What I Gained
After adopting this pattern across all my projects:
- Zero type drift between API contracts
- Runtime validation that catches bad data before it hits your database
- Autocomplete everywhere from a single schema definition
- Less code because validation logic isn't duplicated
The initial setup takes a bit of planning, but the long-term payoff in reliability and developer experience is significant.