TypeScript has revolutionized the way we write JavaScript applications by adding static type definitions. These type definitions help catch errors early during the development phase, leading to more robust and maintainable code. However, TypeScript's type checking only occurs at compile-time, leaving a gap when it comes to runtime validation. This is where Zod, a powerful schema declaration and validation library, comes into play.
Why Use Zod?
While TypeScript ensures that your code adheres to the specified types during compilation, it does not perform any type checks at runtime. This can lead to unexpected errors, especially when dealing with external data sources like API responses or user inputs. Zod addresses this limitation by providing runtime type-checking capabilities, making sure that the data conforms to the expected types even after the code has been compiled.
With Zod, you can define schemas that describe the shape and type of your data. These schemas can then be used to validate incoming data at runtime, ensuring that the data matches the expected format before it is processed by your application.
Setting Up Zod
Before we dive into the details of using Zod, let's set it up in a TypeScript project.
Installation
You can install Zod using npm or yarn by running the following commands:
npm install zod
Or, if you prefer yarn:
yarn add zod
Configuration
To get the most out of Zod, make sure your tsconfig.json
is configured to enable strict type-checking. Here's an example configuration:
{
"compilerOptions": {
"strict": true
}
}
Basic Usage of Zod
Zod makes it easy to define and validate schemas for various data types. Let's start with some basic examples.
Defining Simple Schemas
To define a schema for a simple data type like a string, you can use the following code:
import { z } from 'zod';
const stringSchema = z.string();
Validating Data with Schemas
Once you have defined a schema, you can use it to validate data using the parse
method. If the data does not match the schema, Zod will throw an error. Here is an example:
const result = stringSchema.parse('Hello'); // Valid
If you try to validate data that does not match the schema, Zod will throw a ZodError
:
try {
stringSchema.parse(123); // Throws ZodError
} catch (error) {
console.error(error.errors); // Output validation errors
}
Advanced Usage of Zod
Zod is not limited to simple data types; it can handle complex objects, arrays, and even custom validations. Let's explore some of these advanced features.
Defining Object Schemas
You can define schemas for objects by specifying the structure and types of their properties:
const UserSchema = z.object({
username: z.string().min(5, 'Username must be at least 5 characters'),
email: z.string().email('Invalid email format'),
password: z.string().min(8, 'Password must contain at least 8 characters'),
age: z.number().optional(), // Optional property
});
const validUserData = {
username: 'johnsmith',
email: 'john@example.com',
password: 'strongpassword123',
};
try {
const myUser = UserSchema.parse(validUserData);
console.log(myUser);
} catch (error) {
console.error(error.errors);
}
Arrays and Tuple Schemas
Zod allows you to define schemas for arrays and tuples. For example, to validate an array of strings or a tuple containing a string and a number:
const stringArray = z.array(z.string());
const tupleSchema = z.tuple([z.string(), z.number()]);
try {
const arrayResult = stringArray.parse(['apple', 'banana']); // Valid
const tupleResult = tupleSchema.parse(['hello', 42]); // Valid
console.log(arrayResult, tupleResult);
} catch (error) {
console.error(error.errors);
}
Nested and Composed Schemas
You can create more complex schemas by nesting objects and composing schemas using methods like .extend()
and .merge()
:
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string().length(5, 'Invalid zip code'),
});
const UserWithAddressSchema = UserSchema.extend({ address: AddressSchema });
const userDataWithAddress = {
username: 'johndoe',
email: 'john@example.com',
password: 'securepassword',
address: {
street: '123 Main St',
city: 'Metropolis',
zipCode: '12345',
},
};
try {
const user = UserWithAddressSchema.parse(userDataWithAddress);
console.log(user);
} catch (error) {
console.error(error.errors);
}
Custom Validations and Refinements
Zod allows for custom validation logic using the .refine()
method. This is useful for adding validation rules that go beyond the basic schema definitions:
const PositiveNumberSchema = z.number().refine((val) => val > 0, {
message: 'Number must be positive',
});
try {
const number = PositiveNumberSchema.parse(10); // Valid
console.log(number);
} catch (error) {
console.error(error.errors);
}
try {
PositiveNumberSchema.parse(-5); // Throws ZodError
} catch (error) {
console.error(error.errors);
}
Transformations
The .transform()
method allows you to modify data during validation. This can be useful for normalizing inputs or applying business logic:
const trimmedStringSchema = z.string().transform((val) => val.trim());
try {
const trimmed = trimmedStringSchema.parse(' hello '); // Transforms to 'hello'
console.log(trimmed);
} catch (error) {
console.error(error.errors);
}
Practical Applications and Examples
Zod is highly versatile and can be applied to various scenarios. Here are some practical applications.
API Response Validation
When interacting with external APIs, it's crucial to ensure that the data received matches the expected structure. Zod can be used for this purpose:
const response = await fetch('https://api.example.com/data');
const data = await response.json();
try {
const parsedData = UserSchema.parse(data);
console.log(parsedData);
} catch (error) {
console.error('Invalid API response:', error.errors);
}
Form Validation in React
Zod can be integrated with React to validate form inputs. Here's an example of a simple form validation:
import React, { useState } from 'react';
import { z } from 'zod';
const formSchema = z.object({
username: z.string().min(3, 'Username must be at least 3 characters'),
email: z.string().email('Invalid email'),
});
function App() {
const [formData, setFormData] = useState({ username: '', email: '' });
const [errors, setErrors] = useState({ username: '', email: '' });
const validate = () => {
try {
formSchema.parse(formData);
setErrors({ username: '', email: '' });
return true;
} catch (error) {
if (error instanceof z.ZodError) {
const newErrors = error.errors.reduce((acc, err) => {
acc[err.path[0]] = err.message;
return acc;
}, {});
setErrors(newErrors);
}
return false;
}
};
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('Form data is valid:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username</label>
<input name="username" value={formData.username} onChange={handleChange} />
{errors.username && <span>{errors.username}</span>}
</div>
<div>
<label>Email</label>
<input name="email" value={formData.email} onChange={handleChange} />
{errors.email && <span>{errors.email}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default App;
Type Inference
Zod also provides type inference, allowing you to extract TypeScript types from schemas. This can be useful to avoid duplicating type definitions:
const UserSchema = z.object({
username: z.string(),
email: z.string(),
});
type User = z.infer<typeof UserSchema>;
const validateUser = (user: User) => {
// user is now strongly typed
};
Conclusion
Zod is an indispensable tool for TypeScript developers, providing robust runtime type-checking to complement TypeScript's compile-time checks. By defining schemas and validating data at runtime, you can ensure that your application handles data consistently and correctly, leading to more reliable and maintainable code. Whether you are validating API responses, user inputs, or complex objects, Zod offers a versatile and intuitive solution.
For further reading and resources, you can refer to the official Zod documentation and explore various community contributions and tutorials to deepen your understanding of Zod and its applications.