Zod is a TypeScript-first schema definition and validation library that's gaining popularity among React developers. Let's dive into it and everything you need to know to get started with Zod.
What is Zod?
Zod enables developers to define the shape and constraints of data using schemas. It provides robust type inference, which means you get TypeScript types for free based on your Zod schemas.
Creating a Zod Schema
A schema defines the structure and constraints of a piece of data. Here's how you create a basic schema with Zod:
import { z } from 'zod';
const personSchema = z.object({
name: z.string(),
age: z.number().positive(),
email: z.string().email(),
});Validating Data
Once you have a schema, you can validate data against it. If the data doesn't match the schema, Zod will throw an error.
const person = {
name: 'Alice',
age: 25,
email: 'alice@example.com',
};
try {
// Either succeed or throw an error
personSchema.parse(person); // Validation success
} catch (error) {
console.error(error); // Validation failure
}
// Doesn't throws an error, returns object
personSchema.safeParse(person); // ObjectType Inference
Zod's type inference automatically generates TypeScript types from your schemas:
type Person = z.infer<typeof personSchema>;
// Person is now { name: string; age: number; email: string; }Primitives and Complex Types
Zod supports various primitives and complex types:
// Primitives
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
// Complex types
const arraySchema = z.array(z.string());
const tupleSchema = z.tuple([z.string(), z.number()]);
const unionSchema = z.union([z.string(), z.number()]);Validation Options
Zod provides options to customize the validation process:
const partialPersonSchema = personSchema.partial(); // All properties are optional
const requiredPersonSchema = personSchema.required(); // Ensure object isn't undefined
const passedPersonSchema = personSchema.passthrough(); // Ignores unrecognized keysCombining Schemas
Zod allows you to combine schemas to create more complex ones:
const employmentSchema = z.object({
jobTitle: z.string(),
salary: z.number(),
});
const employeeSchema = personSchema.merge(employmentSchema);Custom Validation
You can create custom validation logic using .refine():
const passwordSchema = z.string().refine((val) => val.length >= 8, {
message: 'Password must be at least 8 characters',
});Nested Schemas
You can create complex, nested schemas with Zod to validate objects within objects. This is particularly useful for validating complex data structures.
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zipcode: z.string().length(5),
});
const userSchema = z.object({
name: z.string(),
address: addressSchema, // Nested schema
});Transformations
Zod schemas can transform data as it's being validated, such as converting strings to numbers, trimming whitespace, or applying custom transformations.
const trimmedString = z.string().transform((str) => str.trim());
const stringToNumber = z.string().transform((str) => parseInt(str, 10));Asynchronous Validation
Zod supports asynchronous validation, which is helpful for validations that require database lookups or other async operations:
const usernameSchema = z.string().refine(async (username) => {
const exists = await checkUsernameAvailability(username); // An async function
return !exists;
}, 'Username is already taken');Error Handling
When validation fails, Zod provides detailed error information:
const result = personSchema.safeParse({
name: 'Bob',
age: -10,
email: 'not-an-email',
});
if (!result.success) {
console.log(result.error.errors); // Array of error details
}Custom Error Messages
Zod allows you to define custom error messages for each validation rule, making it easier for users to understand what went wrong.
const loginSchema = z.object({
username: z.string().min(1, 'Username cannot be empty'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});Using Zod with React-Hook-Form: Advanced
When integrating Zod with react-hook-form, you can take advantage of advanced features like conditional validation and using external validation APIs.
import { zodResolver } from '@hookform/resolvers/zod';
import { Controller, useForm } from 'react-hook-form';
const formSchema = z.object({
accountType: z.enum(['personal', 'business']),
businessName: z
.string()
.optional()
.refine((name, context) => {
// Only validate if accountType is 'business'
if (context.parent.accountType === 'business') {
return name?.trim().length > 0;
}
return true;
}, 'Business name is required for business accounts'),
});
type FormInputs = z.infer<typeof formSchema>;
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm<FormInputs>({
resolver: zodResolver(formSchema),
});
const MyForm = () => {
const accountType = watch('accountType');
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<select {...register('accountType')}>
<option value="personal">Personal</option>
<option value="business">Business</option>
</select>
{accountType === 'business' && <input {...register('businessName')} />}
{errors.businessName && <p>{errors.businessName.message}</p>}
<button type="submit">Submit</button>
</form>
);
};Conclusion
Zod is a powerful tool for any React developer looking to enforce data integrity with a simple, yet powerful API. Start integrating Zod into your next projects today and experience the benefits of a well-validated application.
