Zod vs. Valibot: Which Validation Library is Right for Your TypeScript Project?

Zod vs. Valibot: Which Validation Library is Right for Your TypeScript Project?

Zod Vs Valibot.

In a TypeScript world, solid validateing libraries an are essential to your data-structures adheres expected types and restraints. While Zod and Valibot are libraries performing similar functions. It helps on reducing the boilerplate for validation in both libraries and catching error messages early, enforcing data integrity. Both two are available, So what should you choose? I am going to cover the features, performance and use-cases of Zod and Valibot in this blog post hopefully help you get a clear idea.

Why validation libraries matter?

Now that we've established when you should use a validation library in TypeScript, lets dive into the comparison. With compile-time strong typing, TypeScript ensures type checks as you code but not at runtime so invalid data can still lead to corruption or unexpected behavior. Validation libraries help with this by making sure that your data not only fits the type definitions, but also obeys constraints like minimum lengths and formats or ranges.

What are Zod and Valibot?

Zod, TypeScript first schema declaration and validation. It has a simple and concise syntax. With Zod, you can easily define schemas for your data structures with chasey validation and TypeScript type inference.

Valibot is a validation library that implements this approach and offers it in an flexible yet powerful API for data validation/transformation. It intends to be more featureful than certain other libraries, so it might satisfy the validation requirements for some large form.

Now, let's compare Zod and Valibot across various criteria.

1. Ease of use:

Zod:

Zod offers an intuitive and straightforward API that's easy to learn, especially for developers familiar with TypeScript and functional programming paradigms.

import { z } from "zod";
const UserSchema = z.object({
  name: z.string(),
  age: z.number().int().positive(),
  email: z.string().email(),
  profile_url: z.string().url()
});

const user = UserSchema.parse({
  name: "Sheraz",
  age: 19,
  email: "sheraz.dev121@gmail.com",
  profile_url: "https://www.linkedin.com/in/sheraz-manzoor-842934222/"
});

Valibot:

Valibot also provides a declarative and readable syntax, making it easy to define complex validation rules without much boilerplate.

import * as v from 'valibot'; // 1.2 kB

const UserSchema = v.object({
  name: v.string(),
  age: v.number().int().positive(),
  email: v.pipe(v.string(), v.email()),,
  profile_url: v.string().url(),
});
const user = parse(UserSchema, {
  name: "Sheraz",
  age: 19,
  email: "sheraz.dev121@gmail.com",
  profile_url: "https://www.linkedin.com/in/sheraz-manzoor-842934222/",
});

2. Performance

Zod:

Zod is optimized for performance and handles validation efficiently. However, in extremely performance-critical applications, there might be minor overheads due to its comprehensive feature set.

Valibot:

Valibot emphasizes performance and is designed to be lightweight and fast. Benchmarks often show Valibot performing slightly better than Zod, especially in scenarios involving large and complex schemas.

3. Flexibility

Zod:

Zod is extremely flexible, enabling you to create complex schema definitions or even perform custom validations and data transformations. It also has support for unions, intersections, and recursive schemas.

Valibot:

Valibot allows developers a high level of flexibility to define the complex validation rules and custom validators in no time.

4. Type Inference

Zod:

Zod excels in type inference. By doing that, it auto-generates the TypeScript types from your schemas, so you never forget to update any of them and keep both TsTypes and validation schema up-to-date.

const UserSchema = z.object({
  name: z.string(),
  age: z.number().min(1),
});

type User = z.infer<typeof UserSchema>;
// Equivalent to:
// type User = {
//   name: string;
//   age: number;
// };

Valibot:

Valibot allows developers a high level of flexibility to define the complex validation rules and custom validators in no time.

import * as v from 'valibot';
const UserSchema = v.object({
  name: v.string(),
  age: v.pipe(v.number(), v.minValue(1)),
});

type User = v.InferInput<typeof UserSchema>;

5. Community and ecosystem

Zod:

Zod has a larger and more active community, resulting in extensive documentation, tutorials, and third-party integrations. Its popularity means more resources are available for learning and troubleshooting.

Valibot:

Valibot is relatively newer than Zod but it is also growing steadily. Its documentation is comprehensive, and the community support is increasing, though it may not be as extensive as Zod's yet.

Use Cases

To illustrate how Zod and Valibot can be applied in real-world scenarios, let's explore a few use cases examples.

Simple Data Validation

Zod:

const ProductSchema = z.object({
  id: z.string(),
  price: z.number().positive(),
});

const product = ProductSchema.parse({
  id: "123",
  price: 19.99,
});

Valibot:

import * as v from 'valibot';
const ProductSchema = object({
  id: v.string(),
  price: v.pipe(v.number(), v.minValue(0)),
});

const product = ProductSchema.parse({
  id: "123",
  price: 19.99,
});

Nested Schemas

Zod:

const AddressSchema = z.object({
  street: z.string(),
  city: z.string(),
  zip: z.string().length(5),
});

const UserSchema = z.object({
  name: z.string(),
  address: AddressSchema,
});

const user = UserSchema.parse({
  name: "Bob",
  address: {
    street: "456 Elm St",
    city: "Metropolis",
    zip: "12345",
  },
});

Valibot:

import * as v from 'valibot';
const AddressSchema = v.object({
  street: v.string(),
  city: v.string(),
  zip: v.pipe(v.string(),v.toMinValue(5),v.toMaxValue(5)),
});

const UserSchema = object({
  name: v.string(),
  address: AddressSchema,
});

const user = UserSchema.parse({
  name: "Bob",
  address: {
    street: "456 Elm St",
    city: "Metropolis",
    zip: "12345",
  },
});

Custom Validations

Zod:

const PasswordSchema = z.string().min(8).refine((val) => /[A-Z]/.test(val), {
  message: "Password must contain at least one uppercase letter",
});

const userPassword = PasswordSchema.parse("SecurePass1");

Valibot:

import * as v from 'valibot';
const PasswordSchema= v.custom<string>((password)=>
    typeOf password=== 'string' ? /[A-Z]/.test(password):false
)

const userPassword = PasswordSchema.parse("SecurePass1");

Validating API data

Scenario

Imagine you're building an application that fetches user data from a public API like JSONPlaceholder. You want to ensure that the data returned by the API conforms to your expected schema before using it in your application. If the data doesn't match the schema, you want to handle it gracefully.

Zod:

import { z } from 'zod';

// Define the schema for the expected API data
const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  username: z.string(),
  email: z.string().email(),
  address: z.object({
    street: z.string(),
    city: z.string(),
    zipcode: z.string().regex(/^\d{5}-\d{4}$/),
  }),
});

// Fetch data from the API
async function fetchData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
    const data = await response.json();

    // Validate the data using Zod
    const parsedData = UserSchema.parse(data);
    console.log('Data is valid:', parsedData);
  } catch (error) {
    console.error('Invalid data:', error.errors);
  }
}

fetchData();

Valibot:

import * as v from 'valibot';
// Define the schema for the expected API data
const UserSchema = v.object({
  id: v.number(),
  name: v.string(),
  username: v.string(),
  email: v.pipe(v.string(), v.email()),,
  address: v.object({
    street: v.string(),
    city: v.string(),
    zip: v.pipe(v.string(),v.toMinValue(5),v.toMaxValue(5)),
  }),
});

// Fetch data from the API
async function fetchData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
    const data = await response.json();

    // Validate the data using Valibot
    const parsedData = parse(UserSchema, data);
    console.log('Data is valid:', parsedData);
  } catch (error) {
    console.error('Invalid data:', error);
  }
}

fetchData();

Conclusion

Zod and Valibot are both efficient validation libraries designed for smooth integration with TypeScript, guaranteeing robust solutions for runtime data validation. Here is a brief summary of how to select which option to use.

Choose Zod if:

  • You want a well-established library with a large number of users and ecosystem.

  • You look for cutting-edge features with which you can enrich validation and data transformation.

  • You love enriched documentation and numerous resources for learning the library from scratch.

Choose Valibot if:

  • You would like the active speed and lightness of a library to have a minor influence on your application’s general performance.

  • You enjoy flexible, declarative syntax to define advanced validation rules.

  • You are willing to collaborate with the library that is under consistent upgrading and is regarded as a young solution.

If you have no experience about validating data, I will highy suggest to use Zod first, because it has a huge community and there's lot of information on internet regarding it. But if you have worked with Zod already, I will suggest to use a mix of Zod and Valibot. For small or medium level schemas use zod, as it will be easy to use there without effecting performance. For large and complex schemas use Valibot, as it excels Zod in terms of performance.

Ultimately, both libraries will serve you well in ensuring data integrity and type safety in your TypeScript projects. It might be beneficial to experiment with both in a small project to see which aligns better with your development style and project requirements.

Happy Coding! 😊