zod vs joi vs yup vs class-validator vs class-transformer-validator
Data Validation and Transformation Comparison
1 Year
zodjoiyupclass-validatorclass-transformer-validatorSimilar Packages:
What's Data Validation and Transformation?

Data validation and transformation libraries in JavaScript are essential tools for ensuring that the data your application processes meets specific criteria and formats. These libraries help validate user input, API responses, and any other data to ensure it is correct, complete, and secure before it is used in your application. They can also transform data into the desired format, making them invaluable for tasks like sanitizing input, enforcing data integrity, and preventing security vulnerabilities such as injection attacks. Popular libraries like class-validator, joi, yup, and zod offer various features and approaches to validation, including schema-based validation, decorators, and asynchronous validation, allowing developers to choose the best fit for their needs.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
zod31,003,35538,4661.62 MB59511 hours agoMIT
joi11,912,58521,111531 kB190a year agoBSD-3-Clause
yup7,285,82723,396260 kB2456 months agoMIT
class-validator4,396,77611,4285.13 MB319a month agoMIT
class-transformer-validator43,093203-165 years agoMIT
Feature Comparison: zod vs joi vs yup vs class-validator vs class-transformer-validator

Validation Approach

  • zod:

    zod is a TypeScript-first schema validation library that provides a simple and expressive API for defining schemas. It emphasizes type safety and provides excellent type inference for validated data.

  • joi:

    joi uses a schema-based approach for validation. You define a schema for your data, and joi validates the data against this schema. It supports complex validation scenarios, including nested objects and arrays.

  • yup:

    yup also uses a schema-based approach similar to joi, but it is designed to be more lightweight and intuitive. It is particularly well-suited for front-end applications and integrates nicely with libraries like Formik.

  • class-validator:

    class-validator focuses on validation using decorators. It allows you to define validation rules directly in your class properties, making it easy to validate class instances. It does not handle data transformation.

  • class-transformer-validator:

    class-transformer-validator combines validation and transformation using decorators, making it easy to validate and transform class instances simultaneously. It leverages the power of class-transformer to handle both tasks seamlessly.

TypeScript Support

  • zod:

    zod is built with TypeScript first, providing the best type inference and type safety among the listed libraries. It is designed to take full advantage of TypeScript's features.

  • joi:

    joi provides good TypeScript support, but its dynamic nature can sometimes make type inference challenging, especially with complex schemas.

  • yup:

    yup has solid TypeScript support, and its API is designed to be type-friendly, making it easy to work with in TypeScript projects.

  • class-validator:

    class-validator is designed with TypeScript in mind, making full use of decorators and type annotations to ensure type-safe validation of class instances.

  • class-transformer-validator:

    class-transformer-validator has excellent TypeScript support, leveraging decorators and type annotations to provide type-safe validation and transformation.

Error Handling

  • zod:

    zod offers structured error handling with detailed error reports that include information about the validation failures. It provides a clear and consistent way to handle errors.

  • joi:

    joi provides rich error handling with detailed error objects that include information about the validation failure, making it easy to understand and handle errors programmatically.

  • yup:

    yup also provides detailed error objects for validation failures, which include information about the failed validation and the path to the invalid value, making it easy to display errors in a user-friendly manner.

  • class-validator:

    class-validator offers comprehensive error handling, returning an array of validation errors that include the property name, error message, and constraints. It allows for easy customization of error messages.

  • class-transformer-validator:

    class-transformer-validator provides detailed error messages for validation failures, including information about which property failed validation and why. It also supports custom error messages.

Integration with Frameworks

  • zod:

    zod is gaining popularity in the React and TypeScript communities for its type-safe validation and integration with modern front-end frameworks.

  • joi:

    joi is framework-agnostic and can be used in any JavaScript or TypeScript application. It is commonly used in Node.js applications for validating request payloads.

  • yup:

    yup is particularly popular in React applications and works well with form libraries like Formik and React Hook Form, making it a great choice for front-end validation.

  • class-validator:

    class-validator is widely used in frameworks like NestJS and Angular, where class-based validation is common. It integrates seamlessly with these frameworks' dependency injection and validation systems.

  • class-transformer-validator:

    class-transformer-validator integrates well with frameworks like NestJS, where you can use class-based validation and transformation in your controllers and services.

Ease of Use: Code Examples

  • zod:

    Example of zod

    const { z } = require('zod');
    
    const schema = z.object({
      name: z.string(),
      email: z.string().email(),
    });
    
    schema.safeParse({ name: 'John', email: 'invalid-email' });
    
  • joi:

    Example of joi

    const Joi = require('joi');
    
    const schema = Joi.object({
      name: Joi.string().required(),
      email: Joi.string().email().required(),
    });
    
    const { error, value } = schema.validate({ name: 'John', email: 'invalid-email' });
    console.log(error);
    
  • yup:

    Example of yup

    const yup = require('yup');
    
    const schema = yup.object().shape({
      name: yup.string().required(),
      email: yup.string().email().required(),
    });
    
    schema.validate({ name: 'John', email: 'invalid-email' }).catch((err) => {
      console.log(err);
    });
    
  • class-validator:

    Example of class-validator

    import { IsEmail, IsString } from 'class-validator';
    import { validate } from 'class-validator';
    import { plainToClass } from 'class-transformer';
    
    class User {
      @IsString()
      name: string;
    
      @IsEmail()
      email: string;
    }
    
    const user = plainToClass(User, { name: 'John', email: 'invalid-email' });
    const errors = await validate(user);
    console.log(errors);
    
  • class-transformer-validator:

    Example of class-transformer-validator

    import { IsEmail, IsString } from 'class-validator';
    import { validate } from 'class-validator';
    import { plainToClass } from 'class-transformer';
    import { validateAndTransform } from 'class-transformer-validator';
    
    class User {
      @IsString()
      name: string;
    
      @IsEmail()
      email: string;
    }
    
    const user = plainToClass(User, { name: 'John', email: 'invalid-email' });
    const errors = await validateAndTransform(user);
    console.log(errors);
    
How to Choose: zod vs joi vs yup vs class-validator vs class-transformer-validator
  • zod:

    Select zod if you want a TypeScript-first schema declaration and validation library that provides excellent type inference, validation, and error handling. It is lightweight and designed for modern TypeScript applications.

  • joi:

    Opt for joi if you need a powerful and flexible schema-based validation library that supports complex validation logic, including nested objects, arrays, and custom validators. It is suitable for both JavaScript and TypeScript projects.

  • yup:

    Choose yup if you prefer a schema builder for value parsing and validation that is inspired by joi but has a smaller footprint and a more modern API. It works well with React and Formik, making it a good choice for front-end applications.

  • class-validator:

    Select class-validator if you are focused solely on validating class instances using decorators. It is a great choice for TypeScript projects where you want to enforce validation rules directly in your class definitions.

  • class-transformer-validator:

    Choose class-transformer-validator if you need a solution that combines validation and transformation, especially when working with TypeScript classes. It is ideal for projects that require both features seamlessly integrated.

README for zod

Zod logo

Zod

TypeScript-first schema validation with static type inference
by @colinhacks


Zod CI status License npm discord server stars

Docs   •   Discord   •   𝕏   •   Bluesky


Featured sponsor: Jazz

jazz logo

Learn more about featured sponsorships




Read the docs →



What is Zod?

Zod is a TypeScript-first validation library. Define a schema and parse some data with it. You'll get back a strongly typed, validated result.

import { z } from "zod/v4";

const User = z.object({
  name: z.string(),
});

// some untrusted data...
const input = {
  /* stuff */
};

// the parsed result is validated and type safe!
const data = User.parse(input);

// so you can use it with confidence :)
console.log(data.name);

Features

  • Zero external dependencies
  • Works in Node.js and all modern browsers
  • Tiny: 2kb core bundle (gzipped)
  • Immutable API: methods return a new instance
  • Concise interface
  • Works with TypeScript and plain JS
  • Built-in JSON Schema conversion
  • Extensive ecosystem

Installation

npm install zod

Basic usage

Before you can do anything else, you need to define a schema. For the purposes of this guide, we'll use a simple object schema.

import { z } from "zod/v4";

const Player = z.object({
  username: z.string(),
  xp: z.number(),
});

Parsing data

Given any Zod schema, use .parse to validate an input. If it's valid, Zod returns a strongly-typed deep clone of the input.

Player.parse({ username: "billie", xp: 100 });
// => returns { username: "billie", xp: 100 }

Note — If your schema uses certain asynchronous APIs like async refinements or transforms, you'll need to use the .parseAsync() method instead.

const schema = z.string().refine(async (val) => val.length <= 8);

await schema.parseAsync("hello");
// => "hello"

Handling errors

When validation fails, the .parse() method will throw a ZodError instance with granular information about the validation issues.

try {
  Player.parse({ username: 42, xp: "100" });
} catch (err) {
  if (err instanceof z.ZodError) {
    err.issues;
    /* [
      {
        expected: 'string',
        code: 'invalid_type',
        path: [ 'username' ],
        message: 'Invalid input: expected string'
      },
      {
        expected: 'number',
        code: 'invalid_type',
        path: [ 'xp' ],
        message: 'Invalid input: expected number'
      }
    ] */
  }
}

To avoid a try/catch block, you can use the .safeParse() method to get back a plain result object containing either the successfully parsed data or a ZodError. The result type is a discriminated union, so you can handle both cases conveniently.

const result = Player.safeParse({ username: 42, xp: "100" });
if (!result.success) {
  result.error; // ZodError instance
} else {
  result.data; // { username: string; xp: number }
}

Note — If your schema uses certain asynchronous APIs like async refinements or transforms, you'll need to use the .safeParseAsync() method instead.

const schema = z.string().refine(async (val) => val.length <= 8);

await schema.safeParseAsync("hello");
// => { success: true; data: "hello" }

Inferring types

Zod infers a static type from your schema definitions. You can extract this type with the z.infer<> utility and use it however you like.

const Player = z.object({
  username: z.string(),
  xp: z.number(),
});

// extract the inferred type
type Player = z.infer<typeof Player>;

// use it in your code
const player: Player = { username: "billie", xp: 100 };

In some cases, the input & output types of a schema can diverge. For instance, the .transform() API can convert the input from one type to another. In these cases, you can extract the input and output types independently:

const mySchema = z.string().transform((val) => val.length);

type MySchemaIn = z.input<typeof mySchema>;
// => string

type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema>
// number