munesoftvalidatex
All packages
Core 319/mo
@munesoft/

validatex

The simplest and most powerful schema validation library for JavaScript & TypeScript

Installation

$ npm install @munesoft/validatex

Documentation

@munesoft/validatex

The last validation library you'll ever need.

Validate JSON, APIs, environment variables, and forms — all with one simple, consistent API.

npm version license TypeScript


Why validatex?

| 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 |


Installation

bash
npm install @munesoft/validatex

Quickstart — 5 lines

ts
import { 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 failure

Core API

Primitive types

ts
v.string()           // any string
v.number()           // any number
v.boolean()          // true | false

Semantic types

ts
v.email()            // valid email address
v.url()              // valid URL
v.uuid()             // UUID v1–v5
v.date()             // parseable date string
v.json()             // valid JSON string

Complex types

ts
v.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

Fluent API — chainable constraints

ts
// 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()

Optional, default & required

ts
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"
});

Transformations

Applied after validation, before the value is returned:

ts
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()

Custom validators

ts
// 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;
});

Object validation

Nested objects

ts
const schema = v.object({
  id: v.string().uuid(),
  profile: v.object({
    name: v.string().min(2),
    age: v.number().min(0),
  }),
});

Strict mode — reject unknown keys

ts
v.object({ name: v.string() }).strict()
// { name: "Alice", extra: 1 }  → error: Unknown field "extra"

Cross-field refinements

ts
v.object({
  password: v.string().min(8),
  confirm: v.string(),
}).refine((data) =>
  data.password === data.confirm ? true : "Passwords do not match"
);

Schema composition

ts
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 schemas

Safe parsing — never throws

ts
const 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
}

Error handling

ts
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
  }
}

API validation

ts
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:

ts
import { validateRequestSafe } from "@munesoft/validatex";

const result = validateRequestSafe(req.body, schema);
if (!result.success) {
  return res.status(400).json({ errors: result.errors });
}

Environment variable validation

ts
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.


Query parameter validation

ts
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)
});

TypeScript type inference

ts
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";
// }

Union types

ts
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() }),
]);

Comparison

| 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 | ✅ | ✅ | ✅ |


Complete example

ts
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);
});

License

MIT — Built by Munesoft