Ajv
Library
Ajv
Overview
Ajv stands for "Another JSON Schema validator" and is the world's fastest JSON Schema validation library. It supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927), working in both Node.js and browsers. Featuring high performance and compile-time optimization, Ajv remains the de facto standard for JSON Schema validation in the JavaScript ecosystem as of 2025.
Details
Ajv 9 is the latest stable version as of 2025, providing comprehensive support for all JSON Schema versions. The name stands for "A"nother "j"SON "v"alidator, delivering industry-standard functionality with a simple name. With 12k+ GitHub stars, Ajv is internally used by many libraries including express-validator, fastify, and JSON Schema Editor. Designed with validation runtime performance as the top priority, it achieves high-speed execution through compiled validation functions.
Key Features
- Fastest Performance: Optimized execution through compiled validation functions
- Complete JSON Schema Support: Compliance with all draft versions and latest specifications
- Zero Dependencies: Lightweight design without external library dependencies
- Full TypeScript Support: Type-safe validation and compile-time checking
- Custom Keywords: Extensible with custom validation rules
- Detailed Error Information: Comprehensive error reports useful for debugging
Pros and Cons
Pros
- Industry-leading validation performance
- High interoperability with complete JSON Schema standard compliance
- Rich community and ecosystem
- Excellent TypeScript integration
- Multi-environment support (Node.js, browsers, CDN)
- Detailed and understandable error messages
Cons
- Somewhat high learning curve (requires understanding of JSON Schema specifications)
- Setup time for complex schemas
- Complexity in defining custom keywords
- Memory usage with large schemas
- Not applicable to non-JSON format data
- Requires separate library for TypeScript type inference
Reference Pages
Code Examples
Installation and Basic Setup
# Installing Ajv
npm install ajv
yarn add ajv
pnpm add ajv
bun add ajv
# TypeScript definitions included
# Additional format support (optional)
npm install ajv-formats
Basic Schema Definition and Validation
const Ajv = require("ajv");
const ajv = new Ajv(); // options: {allErrors: true}
// Basic JSON Schema definition
const schema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number", minimum: 0 },
email: { type: "string", format: "email" }
},
required: ["name", "age"],
additionalProperties: false
};
// Schema compilation
const validate = ajv.compile(schema);
// Data validation
const userData = { name: "John Doe", age: 30, email: "[email protected]" };
const valid = validate(userData);
if (valid) {
console.log("Validation successful");
} else {
console.log("Validation errors:", validate.errors);
/*
[
{
instancePath: "/email",
schemaPath: "#/properties/email/format",
keyword: "format",
params: { format: "email" },
message: "must match format \"email\""
}
]
*/
}
// Type examples
const stringSchema = { type: "string" };
const numberSchema = { type: "number" };
const booleanSchema = { type: "boolean" };
const arraySchema = { type: "array", items: { type: "string" } };
const objectSchema = { type: "object" };
// Validation execution
stringSchema.parse = ajv.compile(stringSchema);
console.log(stringSchema.parse("Hello")); // true
console.log(stringSchema.parse(123)); // false
Advanced Validation Rules and Custom Validators
// Number range specification
const ageSchema = {
type: "number",
minimum: 0,
maximum: 150,
multipleOf: 1 // integers only
};
// String detailed validation
const passwordSchema = {
type: "string",
minLength: 8,
maxLength: 50,
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)" // contains uppercase, lowercase, and digits
};
// Arrays and nested objects
const productSchema = {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
name: { type: "string", minLength: 1 },
price: { type: "number", minimum: 0 },
categories: {
type: "array",
items: { type: "string" },
minItems: 1
},
metadata: {
type: "object",
additionalProperties: { type: "string" }
}
},
required: ["id", "name", "price"],
additionalProperties: false
};
// Conditional validation (if/then/else)
const userSchema = {
type: "object",
properties: {
role: { type: "string", enum: ["admin", "user", "guest"] },
permissions: { type: "array", items: { type: "string" } },
department: { type: "string" }
},
if: { properties: { role: { const: "admin" } } },
then: { required: ["permissions"] },
else: {
if: { properties: { role: { const: "user" } } },
then: { required: ["department"] }
}
};
// Custom keyword definition
ajv.addKeyword({
keyword: "even",
type: "number",
schemaType: "boolean",
compile: function(schemaVal) {
return function validate(data) {
if (schemaVal) {
return data % 2 === 0;
}
return true;
};
}
});
// Custom keyword usage
const evenNumberSchema = {
type: "number",
even: true
};
const validateEven = ajv.compile(evenNumberSchema);
console.log(validateEven(4)); // true
console.log(validateEven(3)); // false
Framework Integration (Express, Fastify, NestJS, etc.)
// Express.js middleware example
const express = require('express');
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const ajv = new Ajv();
addFormats(ajv); // Add standard formats (email, uri, etc.)
const app = express();
app.use(express.json());
// Validation middleware
const validateRequest = (schema) => {
const validate = ajv.compile(schema);
return (req, res, next) => {
const valid = validate(req.body);
if (!valid) {
return res.status(400).json({
error: "Validation error",
details: validate.errors
});
}
next();
};
};
// API endpoint usage
const createUserSchema = {
type: "object",
properties: {
name: { type: "string", minLength: 1, maxLength: 100 },
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 18, maximum: 120 }
},
required: ["name", "email", "age"],
additionalProperties: false
};
app.post('/users', validateRequest(createUserSchema), (req, res) => {
// req.body can be accessed in a type-safe manner
res.json({ message: "User creation successful", user: req.body });
});
// Fastify integration example
const fastify = require('fastify')({ logger: true });
const userRouteSchema = {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
};
fastify.post('/users', { schema: userRouteSchema }, async (request, reply) => {
// request is automatically validated
return { id: 'generated-id', ...request.body };
});
// NestJS ValidationPipe integration
import { Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
import Ajv from 'ajv';
@Injectable()
export class AjvValidationPipe implements PipeTransform {
private ajv = new Ajv();
constructor(private schema: any) {}
transform(value: any) {
const validate = this.ajv.compile(this.schema);
const isValid = validate(value);
if (!isValid) {
throw new BadRequestException({
message: 'Validation error',
errors: validate.errors
});
}
return value;
}
}
Error Handling and Custom Error Messages
// Detailed error handling
const schema = {
type: "object",
properties: {
name: { type: "string", minLength: 2 },
age: { type: "number", minimum: 0 }
},
required: ["name", "age"]
};
const validate = ajv.compile(schema);
const result = validate({ name: "A", age: -1 });
if (!result) {
console.log("Error details:", validate.errors);
/*
[
{
instancePath: "/name",
schemaPath: "#/properties/name/minLength",
keyword: "minLength",
params: { limit: 2 },
message: "must NOT have fewer than 2 characters"
},
{
instancePath: "/age",
schemaPath: "#/properties/age/minimum",
keyword: "minimum",
params: { comparison: ">=", limit: 0 },
message: "must be >= 0"
}
]
*/
}
// Custom error messages
const schemaWithCustomMessages = {
type: "object",
properties: {
email: {
type: "string",
format: "email",
errorMessage: "Please enter a valid email address"
},
password: {
type: "string",
minLength: 8,
errorMessage: {
minLength: "Password must be at least 8 characters long"
}
}
},
required: ["email", "password"],
errorMessage: {
required: {
email: "Email is required",
password: "Password is required"
}
}
};
// Error message plugin usage
const ajvErrors = require('ajv-errors');
ajvErrors(ajv);
// Error information organization
function formatErrors(errors) {
const formattedErrors = {};
errors.forEach(error => {
const field = error.instancePath.slice(1) || error.params?.missingProperty;
if (field) {
formattedErrors[field] = error.message;
}
});
return formattedErrors;
}
// Usage example
const userData = { email: "invalid-email", password: "123" };
const isValid = validate(userData);
if (!isValid) {
const fieldErrors = formatErrors(validate.errors);
console.log(fieldErrors);
// { email: "Please enter a valid email address", password: "Password must be at least 8 characters long" }
}
TypeScript Integration and Type Safety
import Ajv, { JSONSchemaType } from 'ajv';
const ajv = new Ajv();
// TypeScript interface definition
interface User {
id: string;
name: string;
email: string;
age: number;
active?: boolean;
}
// JSON Schema corresponding to TypeScript type
const userSchema: JSONSchemaType<User> = {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
email: { type: "string", format: "email" },
age: { type: "number", minimum: 0 },
active: { type: "boolean", nullable: true }
},
required: ["id", "name", "email", "age"],
additionalProperties: false
};
// Type-safe validation function
const validateUser = ajv.compile(userSchema);
// Use as type guard function
function isValidUser(data: unknown): data is User {
return validateUser(data);
}
// Usage example
const userData: unknown = {
id: "12345",
name: "John Doe",
email: "[email protected]",
age: 30
};
if (isValidUser(userData)) {
// Here userData is treated as User type
console.log(`User name: ${userData.name}`);
console.log(`Age: ${userData.age}`);
} else {
console.log("Invalid user data:", validateUser.errors);
}
// Complex type example
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
timestamp: string;
}
const createApiResponseSchema = <T>(dataSchema: JSONSchemaType<T>): JSONSchemaType<ApiResponse<T>> => ({
type: "object",
properties: {
success: { type: "boolean" },
data: { ...dataSchema, nullable: true },
error: { type: "string", nullable: true },
timestamp: { type: "string", format: "date-time" }
},
required: ["success", "timestamp"],
additionalProperties: false
});
// Generic type usage
const userApiResponseSchema = createApiResponseSchema(userSchema);
const validateUserApiResponse = ajv.compile(userApiResponseSchema);
// External API response validation
async function fetchUser(id: string): Promise<ApiResponse<User> | null> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
if (validateUserApiResponse(data)) {
return data; // Type-safely return as ApiResponse<User>
}
console.error("API response validation error:", validateUserApiResponse.errors);
return null;
}
Performance Optimization and Benchmarking
// Schema pre-compilation (recommended)
const schemas = {
user: ajv.compile({
type: "object",
properties: {
name: { type: "string" },
email: { type: "string", format: "email" }
},
required: ["name", "email"]
}),
product: ajv.compile({
type: "object",
properties: {
id: { type: "string" },
price: { type: "number", minimum: 0 }
},
required: ["id", "price"]
})
};
// High-speed validation of large datasets
function validateBulkData(items, validator) {
const startTime = performance.now();
const results = items.map(item => validator(item));
const endTime = performance.now();
console.log(`Validation time for ${items.length} items: ${endTime - startTime}ms`);
return results;
}
// Memory-efficient validation
const ajvFast = new Ajv({
code: { optimize: true },
allErrors: false, // Stop at first error (faster)
verbose: false, // Reduce detailed information
strict: false // Disable strict mode (faster)
});
// Stream data validation
const stream = require('stream');
class ValidationStream extends stream.Transform {
constructor(schema) {
super({ objectMode: true });
this.validate = ajv.compile(schema);
}
_transform(chunk, encoding, callback) {
if (this.validate(chunk)) {
this.push(chunk);
} else {
this.emit('error', new Error(`Validation error: ${JSON.stringify(this.validate.errors)}`));
}
callback();
}
}
// Usage example
const validationStream = new ValidationStream(userSchema);
validationStream.on('error', (error) => {
console.error('Stream validation error:', error.message);
});