ajv vs class-validator vs joi vs yup vs zod
Runtime Schema Validation for TypeScript and JavaScript
ajvclass-validatorjoiyupzodSimilar Packages:

Runtime Schema Validation for TypeScript and JavaScript

ajv, class-validator, joi, yup, and zod are libraries used to validate data structures at runtime. They ensure that incoming data matches expected shapes before your application processes it. While ajv follows the official JSON Schema standard, joi, yup, and zod use builder patterns to define rules. class-validator relies on TypeScript decorators attached to class properties. These tools help prevent bugs caused by invalid data in forms, API responses, and configuration files.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
ajv014,8611.03 MB3836 months agoMIT
class-validator011,8405.33 MB3208 months agoMIT
joi021,1641.89 MB204a month agoBSD-3-Clause
yup023,655270 kB260a year agoMIT
zod044,1216.15 MB902 days agoMIT

Runtime Validation Libraries: ajv vs class-validator vs joi vs yup vs zod

Validating data is a core part of building reliable web applications. Whether you are checking form inputs or verifying API responses, you need tools that ensure data matches your expectations. ajv, class-validator, joi, yup, and zod all solve this problem, but they approach it from different angles. Let's compare how they handle schema definition, TypeScript integration, and error handling.

📝 Defining Schemas: Standards vs Builders vs Decorators

The way you define what valid data looks like varies significantly across these libraries.

ajv uses the official JSON Schema standard.

  • You write a plain JavaScript object that follows a specific specification.
  • This makes it easy to share schemas with other systems that support JSON Schema.
// ajv: JSON Schema object
const schema = {
  type: "object",
  properties: {
    name: { type: "string" },
    age: { type: "number" }
  },
  required: ["name", "age"]
};

class-validator uses TypeScript decorators on class properties.

  • You define a class and add validation rules directly to the fields.
  • This keeps validation logic close to your data model definition.
// class-validator: Decorator class
import { IsString, IsNumber } from "class-validator";

class User {
  @IsString()
  name: string;

  @IsNumber()
  age: number;
}

joi uses a chainable builder API.

  • You call methods like .string() and .required() to build rules.
  • The syntax is expressive and reads like a sentence.
// joi: Chainable schema
const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required()
});

yup also uses a chainable builder API, similar to joi.

  • It is designed to be lightweight and works well with form libraries.
  • The syntax is concise and familiar to many React developers.
// yup: Chainable schema
const schema = yup.object({
  name: yup.string().required(),
  age: yup.number().required()
});

zod uses a chainable builder API focused on TypeScript.

  • You define schemas using functions like z.string() and z.object().
  • The API is designed to map directly to TypeScript types.
// zod: Chainable schema
const schema = z.object({
  name: z.string(),
  age: z.number()
});

🛡️ TypeScript Integration: Inference vs Manual Types

Type safety is a major concern for frontend architects. Some libraries help you avoid writing types twice.

ajv does not infer TypeScript types.

  • You must define your TypeScript interface separately from the JSON Schema.
  • This can lead to mismatches if you update one but not the other.
// ajv: Manual type definition
interface User {
  name: string;
  age: number;
}
// Schema is separate and not linked to interface

class-validator uses the class definition as the type.

  • The class itself acts as the type definition.
  • You do not need separate interfaces, but you must use classes.
// class-validator: Class as type
function validate(user: User) {
  // User type comes from the class
}

joi does not infer TypeScript types natively.

  • You typically define interfaces separately.
  • Community packages exist to bridge the gap, but they are not built-in.
// joi: Manual type definition
interface User {
  name: string;
  age: number;
}
// Schema validation is runtime only

yup supports type inference but requires extra steps.

  • You can use InferType from yup to get a TypeScript type from the schema.
  • It works well but adds a layer of abstraction.
// yup: Type inference
const schema = yup.object({ name: yup.string() });
type User = yup.InferType<typeof schema>;

zod infers TypeScript types automatically.

  • You use z.infer to extract the type directly from the schema.
  • This ensures your runtime validation and static types are always in sync.
// zod: Automatic type inference
const schema = z.object({ name: z.string() });
type User = z.infer<typeof schema>;

✅ Running Validation: Functions vs Methods

Once schemas are defined, you need to check data against them.

ajv compiles schemas into validation functions.

  • You create an instance and compile the schema once for performance.
  • The function returns a boolean indicating success.
// ajv: Compile and validate
const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(data); // true or false

class-validator uses a standalone validate function.

  • You pass an instance of your class to the validator.
  • It returns an array of validation errors.
// class-validator: Validate instance
const user = new User();
const errors = await validate(user);

joi uses the .validate() method on the schema.

  • It returns an object containing either the value or an error.
  • You check the error property to see if validation failed.
// joi: Validate method
const { error, value } = schema.validate(data);

yup uses the .validate() method which returns a promise.

  • It resolves with the validated value or rejects with an error.
  • This fits well with async/await patterns in React.
// yup: Async validate
try {
  const value = await schema.validate(data);
} catch (err) {
  // Handle error
}

zod uses .parse() or .safeParse().

  • .parse() throws an error if invalid.
  • .safeParse() returns an object with success status, avoiding try/catch.
// zod: Safe parse
const result = schema.safeParse(data);
if (result.success) { /* use result.data */ }

❌ Handling Errors: Structures and Messages

When validation fails, you need clear error messages to show users.

ajv stores errors on the instance after validation.

  • You access validate.errors to see what went wrong.
  • The structure follows the JSON Schema error format.
// ajv: Error collection
if (!validate(data)) {
  console.log(validate.errors);
}

class-validator returns an array of error objects.

  • Each object describes the property and the constraint failed.
  • You can map these to user-friendly messages easily.
// class-validator: Error array
const errors = await validate(user);
errors.forEach(e => console.log(e.constraints));

joi provides a detailed error object.

  • The error contains a message and a path to the invalid field.
  • It is very descriptive out of the box.
// joi: Error details
if (error) {
  console.log(error.details[0].message);
}

yup throws a ValidationError with path information.

  • You can catch this error and read the path and message.
  • Form libraries like Formik handle this automatically.
// yup: Catch error
try {
  await schema.validate(data);
} catch (err) {
  console.log(err.path, err.message);
}

zod returns formatted errors in the result object.

  • safeParse gives you a error.format() method for easy access.
  • It is designed to be easy to transform for UI display.
// zod: Format errors
if (!result.success) {
  console.log(result.error.format());
}

📊 Summary Table

Featureajvclass-validatorjoiyupzod
Schema StyleJSON Schema ObjectClass DecoratorsChainable BuilderChainable BuilderChainable Builder
Type Inference❌ Manual✅ Via Class❌ Manual✅ Via Helper✅ Built-in
Validation StyleCompiled FunctionClass InstanceMethod .validate()Async .validate().parse() / .safeParse()
Error OutputInstance PropertyError ArrayError ObjectThrown ErrorResult Object
DependenciesFewReflect MetadataNoneNoneZero

💡 Final Recommendation

Choosing the right library depends on your project's architecture and type safety needs.

ajv is the best choice for strict JSON Schema compliance and high-performance validation in large-scale systems. It is ideal when interoperability with other tools is a priority.

class-validator fits best in backend-heavy or full-stack TypeScript projects using NestJS where class-based models are already standard.

joi remains a solid option for legacy Node.js projects but is less common in modern frontend bundles due to size and lack of type inference.

yup is a reliable choice for React forms, especially if you are already using Formik. It offers stability and a mature ecosystem.

zod is the top recommendation for new TypeScript projects. Its automatic type inference reduces boilerplate and keeps your runtime checks in sync with your compile-time types. It offers the best balance of safety and developer experience for modern frontend development.

How to Choose: ajv vs class-validator vs joi vs yup vs zod

  • ajv:

    Choose ajv if you need strict compliance with the JSON Schema standard or require maximum validation speed for large datasets. It is ideal when sharing schemas between frontend and backend systems that already use JSON Schema. Be aware that it does not infer TypeScript types automatically.

  • class-validator:

    Choose class-validator if your project uses a class-based architecture, such as NestJS, and relies heavily on decorators. It works well when you want validation logic attached directly to your data models. Note that it requires specific TypeScript compiler settings to enable decorators.

  • joi:

    Choose joi if you are maintaining a legacy project that already depends on it or need a very mature feature set for complex Node.js backends. It is less common in modern frontend bundles due to size concerns. Consider lighter alternatives for new client-side projects.

  • yup:

    Choose yup if you are using Formik or need a stable library with a long history of React form integration. It offers a chainable API that is easy to read and modify. It is a solid choice for teams that prioritize stability over cutting-edge TypeScript features.

  • zod:

    Choose zod if you want TypeScript type inference directly from your validation schemas. It has zero dependencies and provides a modern developer experience with minimal setup. It is increasingly becoming the default choice for new TypeScript projects.

README for ajv

Ajv logo

 

Ajv JSON schema validator

The fastest JSON validator for Node.js and browser.

Supports JSON Schema draft-04/06/07/2019-09/2020-12 (draft-04 support requires ajv-draft-04 package) and JSON Type Definition RFC8927.

build npm npm downloads Coverage Status SimpleX Gitter GitHub Sponsors

Ajv sponsors

Mozilla

Microsoft

RetoolTideliftSimpleX

Contributing

More than 100 people contributed to Ajv, and we would love to have you join the development. We welcome implementing new features that will benefit many users and ideas to improve our documentation.

Please review Contributing guidelines and Code components.

Documentation

All documentation is available on the Ajv website.

Some useful site links:

Please sponsor Ajv development

Since I asked to support Ajv development 40 people and 6 organizations contributed via GitHub and OpenCollective - this support helped receiving the MOSS grant!

Your continuing support is very important - the funds will be used to develop and maintain Ajv once the next major version is released.

Please sponsor Ajv via:

Thank you.

Open Collective sponsors

Performance

Ajv generates code to turn JSON Schemas into super-fast validation functions that are efficient for v8 optimization.

Currently Ajv is the fastest and the most standard compliant validator according to these benchmarks:

Performance of different validators by json-schema-benchmark:

performance

Features

Install

To install version 8:

npm install ajv

Getting started

Try it in the Node.js REPL: https://runkit.com/npm/ajv

In JavaScript:

// or ESM/TypeScript import
import Ajv from "ajv"
// Node.js require:
const Ajv = require("ajv")

const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}

const schema = {
  type: "object",
  properties: {
    foo: {type: "integer"},
    bar: {type: "string"},
  },
  required: ["foo"],
  additionalProperties: false,
}

const data = {
  foo: 1,
  bar: "abc",
}

const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) console.log(validate.errors)

Learn how to use Ajv and see more examples in the Guide: getting started

Changes history

See https://github.com/ajv-validator/ajv/releases

Please note: Changes in version 8.0.0

Version 7.0.0

Version 6.0.0.

Code of conduct

Please review and follow the Code of conduct.

Please report any unacceptable behaviour to ajv.validator@gmail.com - it will be reviewed by the project team.

Security contact

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues.

Open-source software support

Ajv is a part of Tidelift subscription - it provides a centralised support to open-source software users, in addition to the support provided by software maintainers.

License

MIT