validor
v1.0.1
Published
A lightweight and extensible TypeScript-first validation library with schema-based validation for Node.js, React, Express, and NestJS.
Downloads
217
Maintainers
Readme
Validor
A lightweight and extensible TypeScript-first validation library with schema-based validation for Node.js, React, Express, and NestJS.
Features
- 🔷 TypeScript-first: Full type safety and inference
- 🎯 Schema-based validation: Declarative and composable schemas
- 🌍 i18n support: Built-in internationalization (English, Uzbek, Russian, Karakalpak)
- ⚡ Lightweight: Zero dependencies, small bundle size
- 🔗 Chainable API: Fluent and intuitive validation chains
- 🎨 Extensible: Easy to customize and extend
- 🛡️ Type-safe: Automatic type inference from schemas
- ✅ Comprehensive: String, Number, Date, Boolean, Object, Array, IPv4 validation
Installation
npm install validoryarn add validorpnpm add validorQuick Start
import { string, number, object } from "validor";
// Define a schema
const userSchema = object({
name: string().min(2).max(50),
email: string().email(),
age: number().min(18).max(120),
});
// Validate data
try {
const user = userSchema.parse({
name: "John Doe",
email: "[email protected]",
age: 25,
});
console.log("Valid user:", user);
} catch (error) {
console.error("Validation error:", error.message);
}Usage
String Validation
import { string } from "validor";
// Basic string validation
const schema = string();
schema.parse("hello"); // ✓ 'hello'
// Required string
const required = string().required();
required.parse(""); // ✗ ValidationError
// Length constraints
const username = string().min(3).max(20);
username.parse("john"); // ✓ 'john'
// Email validation
const email = string().email();
email.parse("[email protected]"); // ✓ '[email protected]'
// URL validation
const url = string().url();
url.parse("https://example.com"); // ✓ 'https://example.com'
// Pattern matching
const phoneNumber = string().regex(/^\+998\d{9}$/);
phoneNumber.parse("+998901234567"); // ✓ '+998901234567'
// String transformations
const normalized = string().trim().lowercase();
normalized.parse(" HELLO "); // ✓ 'hello'
// Non-empty strings
const nonempty = string().nonempty();
nonempty.parse(""); // ✗ ValidationError
// Exact length
const pinCode = string().length(4);
pinCode.parse("1234"); // ✓ '1234'Number Validation
import { number } from "validor";
// Basic number validation
const schema = number();
schema.parse(42); // ✓ 42
// Range validation
const age = number().min(0).max(120);
age.parse(25); // ✓ 25
// Positive/Negative numbers
const positive = number().positive();
positive.parse(10); // ✓ 10
const negative = number().negative();
negative.parse(-5); // ✓ -5
// Non-negative (>= 0)
const nonnegative = number().nonnegative();
nonnegative.parse(0); // ✓ 0
// Integer validation
const integer = number().integer();
integer.parse(42); // ✓ 42
integer.parse(42.5); // ✗ ValidationError
// Finite numbers only
const finite = number().finite();
finite.parse(100); // ✓ 100
finite.parse(Infinity); // ✗ ValidationError
// Multiple of
const evenNumber = number().multipleOf(2);
evenNumber.parse(10); // ✓ 10
// Range check
const percentage = number().between(0, 100);
percentage.parse(50); // ✓ 50Date Validation
import { date } from "validor";
// Basic date validation
const schema = date();
schema.parse(new Date()); // ✓ Date object
// Date can be string, number, or Date object
schema.parse("2024-01-01"); // ✓ Date object
schema.parse(1704067200000); // ✓ Date object
// Minimum date
const futureDate = date().min(new Date("2024-01-01"));
futureDate.parse(new Date("2024-06-01")); // ✓
// Maximum date
const pastDate = date().max(new Date("2024-12-31"));
pastDate.parse(new Date("2024-06-01")); // ✓
// Must be in the past
const birthDate = date().past();
birthDate.parse(new Date("2000-01-01")); // ✓
// Must be in the future
const deadline = date().future();
deadline.parse(new Date("2025-12-31")); // ✓
// Date range
const eventDate = date().between("2024-01-01", "2024-12-31");
eventDate.parse(new Date("2024-06-01")); // ✓
// Transform to ISO string
const isoDate = date().toISOString();
isoDate.parse(new Date()); // ✓ Returns ISO string
// Transform to timestamp
const timestamp = date().toTimestamp();
timestamp.parse(new Date()); // ✓ Returns numberBoolean Validation
import { boolean } from "validor";
// Basic boolean validation
const schema = boolean();
schema.parse(true); // ✓ true
schema.parse(false); // ✓ false
// Coercion mode
const coerced = boolean().coerce();
coerced.parse("true"); // ✓ true
coerced.parse("false"); // ✓ false
coerced.parse(1); // ✓ true
coerced.parse(0); // ✓ false
// Literal true
const mustBeTrue = boolean().true();
mustBeTrue.parse(true); // ✓ true
mustBeTrue.parse(false); // ✗ ValidationError
// Literal false
const mustBeFalse = boolean().false();
mustBeFalse.parse(false); // ✓ false
mustBeFalse.parse(true); // ✗ ValidationError
// Custom truthy values
const custom = boolean().coerce().addTruthy(["yes", "on"]);
custom.parse("yes"); // ✓ true
// Custom falsy values
const customFalsy = boolean().coerce().addFalsy(["no", "off"]);
customFalsy.parse("no"); // ✓ falseObject Validation
import { object, string, number } from "validor";
// Define object schema
const userSchema = object({
name: string().min(2),
email: string().email(),
age: number().min(18),
});
// Validate
const user = userSchema.parse({
name: "John",
email: "[email protected]",
age: 25,
});
// Strict mode (no extra keys allowed)
const strictSchema = object({
id: number(),
name: string(),
}).strict();
strictSchema.parse({
id: 1,
name: "John",
extra: "not allowed", // ✗ ValidationError
});
// Optional fields
const optionalSchema = object({
name: string(),
nickname: string().optional(),
});
optionalSchema.parse({ name: "John" }); // ✓
// Nullable fields
const nullableSchema = object({
name: string(),
middleName: string().nullable(),
});
nullableSchema.parse({
name: "John",
middleName: null,
}); // ✓
// Nested objects
const addressSchema = object({
street: string(),
city: string(),
country: string(),
});
const personSchema = object({
name: string(),
address: addressSchema,
});
personSchema.parse({
name: "John",
address: {
street: "123 Main St",
city: "New York",
country: "USA",
},
});Array Validation
import { array, string, number } from "validor";
// Array of strings
const stringArray = array(string());
stringArray.parse(["a", "b", "c"]); // ✓
// Array of numbers
const numberArray = array(number());
numberArray.parse([1, 2, 3]); // ✓
// Array length constraints
const limitedArray = array(string()).min(1).max(10);
limitedArray.parse(["item"]); // ✓
// Non-empty arrays
const nonemptyArray = array(string()).nonempty();
nonemptyArray.parse([]); // ✗ ValidationError
// Array of objects
const users = array(
object({
name: string(),
age: number(),
})
);
users.parse([
{ name: "John", age: 25 },
{ name: "Jane", age: 30 },
]); // ✓IPv4 Validation
import { ipv4 } from "validor";
// Basic IPv4 validation
const ip = ipv4();
ip.parse("192.168.1.1"); // ✓ '192.168.1.1'
ip.parse("256.1.1.1"); // ✗ ValidationError
// CIDR notation
const cidr = ipv4().cidr();
cidr.parse("192.168.1.0/24"); // ✓ '192.168.1.0/24'
// Require CIDR
const requireCidr = ipv4().cidrRequired();
requireCidr.parse("192.168.1.0/24"); // ✓
requireCidr.parse("192.168.1.1"); // ✗ ValidationError
// Private IP addresses
const privateIp = ipv4().private();
privateIp.parse("192.168.1.1"); // ✓
privateIp.parse("8.8.8.8"); // ✗ ValidationError
// Public IP addresses
const publicIp = ipv4().public();
publicIp.parse("8.8.8.8"); // ✓
publicIp.parse("192.168.1.1"); // ✗ ValidationError
// Loopback addresses
const loopback = ipv4().loopback();
loopback.parse("127.0.0.1"); // ✓
// Not loopback
const notLoopback = ipv4().notLoopback();
notLoopback.parse("192.168.1.1"); // ✓
notLoopback.parse("127.0.0.1"); // ✗ ValidationError
// IP range
const range = ipv4().range("192.168.1.1", "192.168.1.254");
range.parse("192.168.1.100"); // ✓
// Subnet validation
const subnet = ipv4().inSubnet("192.168.1.0/24");
subnet.parse("192.168.1.50"); // ✓
// Not in subnet
const notInSubnet = ipv4().notInSubnet("10.0.0.0/8");
notInSubnet.parse("192.168.1.1"); // ✓Advanced Features
Default Values
import { string, number } from "validor";
// Static default
const name = string().default("Anonymous");
name.parse(undefined); // ✓ 'Anonymous'
// Function default
const timestamp = number().default(() => Date.now());
timestamp.parse(undefined); // ✓ Current timestampCustom Transformations
import { string, number } from "validor";
// Transform string
const upperCase = string().transform((s) => s.toUpperCase());
upperCase.parse("hello"); // ✓ 'HELLO'
// Transform number
const doubled = number().transform((n) => n * 2);
doubled.parse(5); // ✓ 10
// Chain transformations
const processed = string()
.trim()
.lowercase()
.transform((s) => s.replace(/\s+/g, "-"));
processed.parse(" Hello World "); // ✓ 'hello-world'Async Validation
import { string } from "validor";
const schema = string().email();
// Async validation
const result = await schema.validate("[email protected]");
console.log(result); // ✓ '[email protected]'Error Handling
import { object, string, ValidationError } from "validor";
const schema = object({
name: string().min(3),
email: string().email(),
});
try {
schema.parse({
name: "Jo",
email: "invalid",
});
} catch (error) {
if (error instanceof ValidationError) {
// Get all issues
const issues = error.format();
console.log(issues);
// [
// { path: ['name'], message: '...', code: 'too_small' },
// { path: ['email'], message: '...', code: 'invalid_email' }
// ]
// Get formatted errors grouped by field
const formatted = error.formatErrors();
console.log(formatted);
// {
// message: 'Validation failed',
// errors: {
// 'name': ['The name must be at least 3 characters.'],
// 'email': ['The email must be a valid email address.']
// }
// }
}
}Safe Parsing
import { string } from "validor";
const schema = string().email();
// Safe parse (doesn't throw)
const result = schema.safeParse("invalid-email");
if (result.success) {
console.log("Valid:", result.data);
} else {
console.log("Invalid:", result.error);
}Internationalization (i18n)
Validor supports multiple languages out of the box:
- English (en) - Default
- Uzbek (uz) - O'zbekcha
- Russian (ru) - Русский
- Karakalpak (kaa) - Qaraqalpaqsha
Setting Locale
import { setLocale } from "validor";
// Set locale globally
setLocale("kaa"); // Karakalpak
setLocale("en"); // English (default)
setLocale("uz"); // Uzbek
setLocale("ru"); // RussianCustom Messages
import { i18n } from "validor";
// Add custom messages for a locale
i18n.setMessages("en", {
required: "This field is required!",
invalid_email: "Please enter a valid email address.",
});
// Configure with custom messages
i18n.configure("kaa", {
uz: {
required: "Bul maydan toltırılıwı shárt!",
invalid_email: "Durıs email kiritiń",
},
});Per-Field Custom Messages
import { string } from "validor";
const email = string()
.required("Email is required")
.email("Please provide a valid email address");
email.parse(""); // ✗ 'Email is required'
email.parse("invalid"); // ✗ 'Please provide a valid email address'TypeScript Support
Validor is built with TypeScript and provides full type inference:
import { object, string, number, array } from "validor";
const userSchema = object({
id: number(),
name: string(),
email: string().email(),
roles: array(string()),
profile: object({
bio: string().optional(),
age: number().min(0),
}),
});
// Type is automatically inferred
type User = typeof userSchema._type;
// {
// id: number;
// name: string;
// email: string;
// roles: string[];
// profile: {
// bio?: string;
// age: number;
// };
// }
// Parse with type safety
const user: User = userSchema.parse(data);Integration Examples
Express.js Middleware
import express from "express";
import { object, string, number } from "validor";
const app = express();
const createUserSchema = object({
name: string().min(2).max(50),
email: string().email(),
age: number().min(18),
});
app.post("/users", (req, res) => {
try {
const userData = createUserSchema.parse(req.body);
// userData is validated and typed
res.json({ success: true, user: userData });
} catch (error) {
res.status(400).json({
success: false,
error: error.message,
});
}
});NestJS
import { Injectable, BadRequestException } from "@nestjs/common";
import { object, string } from "validor";
const createUserDto = object({
name: string().min(2),
email: string().email(),
});
@Injectable()
export class UserService {
create(data: unknown) {
try {
const validated = createUserDto.parse(data);
// Process validated data
return validated;
} catch (error) {
throw new BadRequestException(error.message);
}
}
}React Form Validation
import { useState } from "react";
import { object, string } from "validor";
const loginSchema = object({
email: string().email(),
password: string().min(8),
});
function LoginForm() {
const [errors, setErrors] = useState({});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
const validated = loginSchema.parse(data);
// Submit validated data
console.log("Valid:", validated);
setErrors({});
} catch (error) {
setErrors(error.formatErrors().errors);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" />
{errors.email && <span>{errors.email[0]}</span>}
<input name="password" type="password" />
{errors.password && <span>{errors.password[0]}</span>}
<button type="submit">Login</button>
</form>
);
}API Reference
Schema Methods
All schemas support these common methods:
.required(message?)- Make field required.optional()- Make field optional.nullable()- Allow null values.default(value | fn)- Set default value.transform(fn)- Transform the value.parse(value)- Validate and return (throws on error).safeParse(value)- Validate and return result object.validate(value)- Async validation
String Methods
.min(n, message?)- Minimum length.max(n, message?)- Maximum length.length(n, message?)- Exact length.email(message?)- Email validation.url(message?)- URL validation.regex(pattern, message?)- Pattern matching.nonempty(message?)- Non-empty string.trim()- Trim whitespace.lowercase()- Convert to lowercase.uppercase()- Convert to uppercase
Number Methods
.min(n, message?)- Minimum value.max(n, message?)- Maximum value.positive(message?)- Must be > 0.negative(message?)- Must be < 0.nonnegative(message?)- Must be >= 0.integer(message?)- Must be integer.finite(message?)- Must be finite.multipleOf(n, message?)- Must be multiple of n.between(min, max, message?)- Range check
Date Methods
.min(date, message?)- Minimum date.max(date, message?)- Maximum date.past(message?)- Must be in past.future(message?)- Must be in future.between(start, end, message?)- Date range.toISOString()- Transform to ISO string.toTimestamp()- Transform to timestamp
Boolean Methods
.coerce()- Enable type coercion.true(message?)- Must be true.false(message?)- Must be false.addTruthy(values)- Add custom truthy values.addFalsy(values)- Add custom falsy values
Object Methods
.strict()- Disallow extra keys.passthrough()- Allow extra keys
Array Methods
.min(n, message?)- Minimum array length.max(n, message?)- Maximum array length.length(n, message?)- Exact array length.nonempty(message?)- Non-empty array
IPv4 Methods
.cidr()- Allow CIDR notation.cidrRequired(message?)- Require CIDR notation.private(message?)- Must be private IP.public(message?)- Must be public IP.loopback(message?)- Must be loopback.notLoopback(message?)- Must not be loopback.range(start, end, message?)- IP range check.inSubnet(subnet, message?)- Must be in subnet.notInSubnet(subnet, message?)- Must not be in subnet
License
MIT © Azizbek Berdimuratov
Credits
Built with ❤️ by the IDLE OSS team.
