npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@munesoft/validatex

v1.2.1

Published

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

Downloads

212

Readme

@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