Introducing IsoValid: A Lightweight Isomorphic Data Validation Library


Isovalid architecture image

πŸš€ Why Data Validation Matters

Data validation is a crucial part of modern web applications, ensuring data integrity, security, and a smooth user experience. However, developers often struggle with:

  • Writing separate validation logic for frontend and backend
  • Managing multiple validation libraries across environments
  • Keeping validation consistent between client and server

Introducing IsoValidβ€”a powerful, isomorphic validation library that allows you to use the same validation logic across both browser and Node.js environments.

πŸ”₯ What is IsoValid?

IsoValid is a lightweight, TypeScript-first validation library designed to unify validation across your entire application stack. Whether you're validating user input in a React form or ensuring API request integrity in an Express server, IsoValid makes it seamless.

🌟 Key Features

βœ… True Isomorphic Support – The same validation code runs in both frontend and backend
βœ… TypeScript-First – Fully typed API for strong type inference
βœ… Minimal Bundle Size – No unnecessary dependencies or bloat
βœ… Intuitive API – Developer-friendly, chainable syntax
βœ… High Performance – Optimized validation with minimal overhead
βœ… Extensible – Easily add custom validators and error messages

πŸ“¦ Installation

Get started with IsoValid in seconds:

npm install isovalid

πŸ”Ή Core Architecture

IsoValid is built on a flexible, extensible architecture that includes:

Base Schema Class

  • Foundation for all validation schemas
  • Handles optional/nullable states
  • Common validation logic across types

Type-Specific Schemas

  • StringSchema – Length, regex, email, trimming, etc.
  • NumberSchema – Min/max, integer, positive/negative checks
  • More types coming soon (Boolean, Array, Object)

Validation Pipeline

  • Multi-stage validation process
  • Supports custom validation functions
  • Returns detailed error reporting

⚑ Usage Examples

String Validation

import { v } from 'isovalid';

const stringSchema = v.string()
.min(2) // Minimum length
.max(50) // Maximum length
.email() // Must be a valid email
.matches(/regex/) // Custom pattern
.trimmed() // Remove extra whitespace
.setOptional() // Allow undefined
.setNullable(); // Allow null

Number Validation

const numberSchema = v.number()
.min(0) // Minimum value
.max(100) // Maximum value
.integer() // Must be an integer
.positive(); // Must be > 0

Validation Results

interface ValidationResult {
valid: boolean;
errors: Array<{ path: string[]; message: string; }>;
}

🎯 Real-World Examples

1. React Form Validation

import { v } from 'isovalid';
import { useState, FormEvent } from 'react';

const userSchema = {
username: v.string().min(3).max(20),
email: v.string().email(),
age: v.number().integer().min(18)
};

function RegistrationForm() {
const [formData, setFormData] = useState({ username: '', email: '', age: '' });
const [errors, setErrors] = useState<Record<string, string>>({});

const validateField = (field: keyof typeof userSchema, value: any) => {
const result = userSchema[field].validate(value);
return result.valid ? null : result.errors[0].message;
};

const handleSubmit = (e: FormEvent) => {
e.preventDefault();
const newErrors: Record<string, string> = {};

Object.entries(formData).forEach(([field, value]) => {
const error = validateField(field as keyof typeof userSchema, value);
if (error) newErrors[field] = error;
});

if (Object.keys(newErrors).length === 0) {
console.log('Submitting:', formData);
} else {
setErrors(newErrors);
}
};

return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Username" onChange={e => setFormData(prev => ({ ...prev, username: e.target.value }))} />
{errors.username && <span>{errors.username}</span>}
<button type="submit">Register</button>
</form>
);
}

2. Express API Validation

import express from 'express';
import { v } from 'isovalid';

const app = express();
app.use(express.json());

const productSchema = {
name: v.string().min(3).max(100),
price: v.number().min(0),
category: v.string().custom(value =>
['electronics', 'books', 'clothing'].includes(value) ? null : 'Invalid category'
)
};

app.post('/api/products', (req, res) => {
const errors = Object.entries(productSchema)
.map(([field, schema]) => ({ field, result: schema.validate(req.body[field]) }))
.filter(({ result }) => !result.valid)
.map(({ field, result }) => ({ field, message: result.errors[0].message }));

if (errors.length > 0) {
return res.status(400).json({ errors });
}

res.status(201).json(req.body);
});

🎯 Best Practices

βœ… Schema Reuse – Define schemas once & share across frontend/backend
βœ… Type Safety – Leverage TypeScript’s inference capabilities
βœ… Performance Optimization – Cache schema instances for reuse
βœ… Error Handling – Always check .valid before using data

πŸ› οΈ Roadmap

πŸš€ Upcoming Features:

πŸ“Œ Get Started with IsoValid

IsoValid is open-source and welcomes contributions! Try it today and share your feedback.

πŸ”— NPM Package: IsoValid on NPM
πŸ”— GitHub Repository: GitHub Repo

Let’s make data validation simpler together! πŸš€