How to Share Zod Schemas for Frontend and Backend


In modern web development, keeping validation logic consistent across frontend and backend is crucial. By using Zod, you can define a single source of truth with shared schemas. When your project is structured as a monorepo, this approach ensures that all applications and services consume the same validation rules, reducing duplication, improving maintainability, and enforcing type safety end-to-end.

This article walks through how to create shared Zod schemas, integrate them into both frontend and backend, and structure them in a monorepo for maximum efficiency.

Creating Shared Zod Schemas

// packages/shared-schemas/userSchema.ts
import { z } from "zod";

export const UserFormSchema = z.object({
  username: z.string().min(3, { message: "Username must be at least 3 characters" }),
  email: z.string().email({ message: "Enter valid email" }),
  password: z.string().min(8, { message: "Password must be at least 8 characters" }),
});

Explanation:

  • This schema validates the username, email, and password fields.
  • Custom error messages improve user experience.
  • Can be imported by any app inside the monorepo.

Using Zod Shared Schemas in the Frontend

// apps/frontend/src/components/UserForm.tsx
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { UserFormSchema } from "@shared-schemas/userSchema";

type UserFormData = z.infer<typeof UserFormSchema>;

export default function UserForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<UserFormData>({
    resolver: zodResolver(UserFormSchema),
  });

  const onSubmit = (data: UserFormData) => {
    console.log("Form Data:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("username")} placeholder="Username" />
      {errors.username && <span>{errors.username.message}</span>}

      <input {...register("email")} placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register("password")} type="password" placeholder="Password" />
      {errors.password && <span>{errors.password.message}</span>}

      <button type="submit">Submit</button>
    </form>
  );
}

Notes:

  • Uses the same schema at backend.
  • Errors are displayed directly to the user with messages defined in the shared schema.

Using Shared Schemas in the Backend

// apps/backend/src/routes/user.ts
import express from "express";
import { UserFormSchema } from "@shared-schemas/userSchema";

const router = express.Router();

router.post("/users", (req, res) => {
  try {
    const validatedData = UserFormSchema.parse(req.body);
    // proceed with validatedData
    res.status(200).json({ message: "User data is valid", data: validatedData });
  } catch (err) {
    res.status(400).json({ errors: err.errors });
  }
});

export default router;

Explanation:

  • Same Zod schema validates incoming API requests.
  • Ensures frontend and backend validation logic are identical.
  • Reduces potential bugs caused by mismatched rules.

Benefits of Using Shared Zod Schemas in a Monorepo

  • Reduced Duplication: No repeated validation logic in different apps/services.
  • End-to-End Type Safety: Catch errors early in both frontend and backend.
  • Consistent Validation: One source of truth eliminates discrepancies.
  • Improved Maintainability: Update schema once, changes reflect everywhere.
  • Scalable Architecture: Works well for large teams and multiple apps.

Conclusion

By leveraging shared Zod schemas in a monorepo, you can achieve type-safe, consistent, and maintainable form validation across all layers of your application. Both frontend and backend consume the same validation rules, reducing bugs, improving developer productivity, and supporting scalable architecture.

This approach is part of a larger Zod validation system that helps you build type-safe, scalable applications.
Explore the complete guide on Zod schema architecture

Sharukhan Avatar

Sharukhan Patan

Sharukhan Patan is the founder of Tecktol. He has worked as a software engineer specializing in full-stack web development.

Popular Posts