Zod: Same schema frontend and backend

The same Zod schema you used in the backend phase for request validation is the schema you want on the frontend for form validation. Same rules, same error messages, same source of truth. The @hookform/resolvers package lets React Hook Form use a Zod schema directly.

LoginForm.tsx
tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const LoginSchema = z.object({
  username: z.string().min(3, 'Username must be at least 3 characters'),
  password: z.string().min(6, 'Password must be at least 6 characters'),
});

type FormValues = z.infer<typeof LoginSchema>;

export function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormValues>({
    resolver: zodResolver(LoginSchema),
  });

  // ... onSubmit and JSX
  return null;
}

The same schema the backend uses, plugged into React Hook Form.

This is where a monorepo shines. The LoginSchema lives in a shared package imported by both the frontend and the backend. A change in one place updates the validation rules in both. Error messages stay consistent. Bug fixes never have to be applied twice.

One schema, two consumers

A single Zod definition feeds both the React Hook Form resolver and the backend validation layer.

Quiz: Quiz

Loading practice…