The last validation library you'll ever need.
Validate JSON, APIs, environment variables, and forms — all with one simple, consistent API.
| Problem | Other libraries | validatex |
|---|---|---|
| Too verbose | AJV requires JSON Schema objects | Just v.string().min(2) |
| Not TypeScript-first | Extra setup and as casts |
Types inferred automatically |
| One use case | Zod is API-only; env needs separate packages | One library for everything |
| Too opinionated | Forces project structure | Zero config, works anywhere |
npm install @munesoft/validateximport { v } from "@munesoft/validatex";
const schema = v.object({
name: v.string().min(2),
age: v.number().min(18),
email: v.email(),
});
schema.parse(data); // throws with readable errors on failurev.string() // any string
v.number() // any number
v.boolean() // true | falsev.email() // valid email address
v.url() // valid URL
v.uuid() // UUID v1–v5
v.date() // parseable date string
v.json() // valid JSON stringv.object({ ... }) // typed object
v.array(v.string()) // typed array
v.union([...]) // one of N types
v.literal("admin") // exact value
v.enum(["a","b"]) // string enum
v.nullable(v.string()) // T | null
v.any() // no validation// Strings
v.string().min(3).max(20).trim().lowercase()
v.string().regex(/^\d+$/, "Must be digits")
v.string().startsWith("sk_").nonempty()
// Numbers
v.number().min(0).max(100).int().positive()
v.number().multipleOf(5).finite()
// Arrays
v.array(v.string()).min(1).max(10).nonempty().unique()v.string().optional() // undefined is allowed
v.number().default(0) // use 0 when field is missing
v.string().required() // explicit (default behaviour)
// In objects:
const schema = v.object({
name: v.string(),
nickname: v.string().optional(), // may be absent
role: v.string().default("user"), // falls back to "user"
});Applied after validation, before the value is returned:
v.string().trim().lowercase()
// Custom transform
v.string().transform((val) => val.replace(/\s+/g, "-"))
// Chain multiple
v.string()
.trim()
.transform((val) => val.replace(/[^a-z0-9]/gi, ""))
.lowercase()// Synchronous
v.string().custom((val) => {
if (!val.startsWith("sk_")) return "API key must start with sk_";
return true;
});
// Async (e.g. database lookup)
v.string().async(async (val) => {
const taken = await db.users.exists({ email: val });
return taken ? "Email already in use" : true;
});const schema = v.object({
id: v.string().uuid(),
profile: v.object({
name: v.string().min(2),
age: v.number().min(0),
}),
});v.object({ name: v.string() }).strict()
// { name: "Alice", extra: 1 } → error: Unknown field "extra"v.object({
password: v.string().min(8),
confirm: v.string(),
}).refine((data) =>
data.password === data.confirm ? true : "Passwords do not match"
);const base = v.object({ id: v.string() });
const user = base.extend({ name: v.string(), email: v.email() });
const admin = user.extend({ role: v.literal("admin") });
// Utilities
user.partial() // all fields optional
user.pick(["id", "name"]) // only id and name
user.omit(["email"]) // everything except email
user.merge(other) // merge two schemasconst result = schema.safeParse(data);
if (!result.success) {
// result.errors is an array of { path, message }
console.log(result.errors);
// [
// { path: "email", message: "Invalid email address" },
// { path: "age", message: "Must be >= 18" }
// ]
} else {
console.log(result.data); // fully typed
}import { ValidatexError } from "@munesoft/validatex";
try {
schema.parse(data);
} catch (e) {
if (e instanceof ValidatexError) {
console.log(e.errors); // structured array
console.log(e.format()); // human-readable multi-line string
}
}import { validateRequest } from "@munesoft/validatex";
// Express / Fastify / Hono etc.
app.post("/users", (req, res) => {
const body = validateRequest(req.body, v.object({
username: v.string().min(3),
email: v.email(),
password: v.string().min(8),
}));
// body is fully typed here
});Or use the safe version for custom error responses:
import { validateRequestSafe } from "@munesoft/validatex";
const result = validateRequestSafe(req.body, schema);
if (!result.success) {
return res.status(400).json({ errors: result.errors });
}import { validateEnv, v } from "@munesoft/validatex";
// Runs at startup — throws immediately if env is misconfigured
const env = validateEnv({
PORT: v.number().default(3000),
DATABASE_URL: v.url(),
API_KEY: v.string().min(20),
DEBUG: v.boolean().default(false),
NODE_ENV: v.enum(["development", "staging", "production"]),
});
// env is fully typed: { PORT: number, DATABASE_URL: string, ... }
app.listen(env.PORT);String→number and string→boolean coercion happens automatically.
import { validateQuery } from "@munesoft/validatex";
app.get("/items", (req, res) => {
const query = validateQuery(req.query, v.object({
page: v.number().int().min(1).default(1),
limit: v.number().int().max(100).default(20),
q: v.string().optional(),
}));
// query.page, query.limit are numbers (not strings)
});import { v, Infer } from "@munesoft/validatex";
const userSchema = v.object({
id: v.string().uuid(),
name: v.string(),
email: v.email(),
role: v.enum(["admin", "user"] as const),
});
type User = Infer<typeof userSchema>;
// {
// id: string;
// name: string;
// email: string;
// role: "admin" | "user";
// }const id = v.union([v.string().uuid(), v.number().int().positive()]);
id.parse("550e8400-e29b-41d4-a716-446655440000"); // ✅
id.parse(42); // ✅
id.parse("invalid"); // ❌
const payload = v.union([
v.object({ type: v.literal("text"), content: v.string() }),
v.object({ type: v.literal("image"), url: v.url() }),
]);| Feature | AJV | Zod | validatex |
|---|---|---|---|
| Simple fluent API | ❌ | ✅ | |
| TypeScript inference | ✅ | ✅ | |
| API validation | ✅ | ✅ | ✅ |
| Env var validation | ❌ | ❌ | ✅ |
| Query param coercion | ❌ | ✅ | |
| Cross-field refinements | ❌ | ✅ | ✅ |
| Async validators | ❌ | ✅ | ✅ |
| Schema composition | ❌ | ✅ | ✅ |
| Zero config | ❌ | ✅ | |
| Browser + Node.js | ✅ | ✅ | ✅ |
import { v, validateEnv, validateRequest, Infer } from "@munesoft/validatex";
// 1. Validate environment at startup
const env = validateEnv({
PORT: v.number().default(3000),
DB_URL: v.url(),
JWT_KEY: v.string().min(32),
});
// 2. Define schemas
const CreateUserSchema = v.object({
username: v.string().min(3).max(20).regex(/^[a-z0-9_]+$/, "Lowercase alphanumeric only"),
email: v.email(),
password: v.string().min(8),
confirm: v.string(),
age: v.number().int().min(18),
role: v.enum(["admin", "user"] as const).default("user"),
}).refine((d) => d.password === d.confirm ? true : "Passwords do not match");
type CreateUser = Infer<typeof CreateUserSchema>;
// 3. Use in route handler
app.post("/users", (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.errors });
}
// result.data is fully typed as CreateUser
createUser(result.data);
});MIT — Built by Munesoft