Skip to content

munesoft/validatex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@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

npm install @munesoft/validatex

Quickstart — 5 lines

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

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

Semantic types

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

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

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

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:

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

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

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

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

Cross-field refinements

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

Schema composition

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

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

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

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

Environment variable validation

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

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

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

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

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

About

Simple, powerful schema validation for TypeScript & JavaScript. Validate JSON, APIs, env vars, and forms with one clean API.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors