A lightweight, dependency-free runtime validation library for TypeScript and JavaScript.
node-validator is designed for production use where you want a small validation core, explicit error reporting, and strong TypeScript inference without pulling in a large schema framework.
safeParse() for result-based control flow and parse() for exception-based control flowstrip, allow, or error>=18npm install @sourceregistry/node-validator
Validator is also exported as v for shorter schema definitions.
import { v } from "@sourceregistry/node-validator";
const userValidator = v.object({
id: v.number({ integer: true, min: 1 }),
name: v.string({ trim: true, non_empty: true, min: 2 }),
email: v.optional(
v.string({ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ })
),
roles: v.array(v.enum(["admin", "user"] as const), { min: 1 }),
});
const result = v.safeParse(userValidator, {
id: 1,
name: " Ada ",
roles: ["admin"],
});
if (result.success) {
console.log(result.data.name); // "Ada"
} else {
console.error(result.errors);
}
Use safeParse() when validation failures are part of normal control flow:
import { v } from "@sourceregistry/node-validator";
const result = v.safeParse(v.number({ min: 1 }), 0);
if (!result.success) {
console.log(result.errors);
}
Use parse() when invalid input should fail fast:
import { SchemaValidationError, v } from "@sourceregistry/node-validator";
try {
const port = v.parse(
v.number({ integer: true, min: 1, max: 65535 }),
3000
);
console.log(port);
} catch (error) {
if (error instanceof SchemaValidationError) {
console.error(error.errors);
}
}
Use safeParseFormData() or parseFormData() when the payload starts as FormData:
const formData = new FormData();
formData.set("name", " Ada ");
formData.append("roles", "admin");
formData.append("roles", "user");
const result = v.safeParseFormData(
v.object({
name: v.string({ trim: true, non_empty: true }),
roles: v.array(v.enum(["admin", "user"] as const), { min: 1 }),
}),
formData
);
Repeated FormData keys are exposed to validators as arrays in insertion order. Single entries remain single values.
const username = v.string({
trim: true,
non_empty: true,
min: 3,
max: 20,
pattern: /^[a-z0-9_]+$/i,
});
const quantity = v.number({
integer: true,
min: 0,
max: 100,
});
const tags = v.array(
v.string({ trim: true, non_empty: true }),
{ max: 10 }
);
const point = v.tuple([
v.number(),
v.number(),
]);
const config = v.object(
{
host: v.string({ non_empty: true }),
port: v.number({ integer: true, min: 1, max: 65535 }),
},
{ unknownKeys: "error" }
);
const envMap = v.record(v.string(), {
key: v.string({ pattern: /^[A-Z_]+$/ }),
});
const nickname = v.optional(v.string());
const archivedAt = v.nullable(v.string());
const retries = v.withDefault(
v.number({ integer: true, min: 0 }),
3
);
const mode = v.enum(["development", "production"] as const);
const id = v.union(
v.number({ integer: true, min: 1 }),
v.string({ non_empty: true })
);
const normalizedEmail = v.transform(
v.string({ trim: true, non_empty: true }),
(value) => value.toLowerCase()
);
const evenNumber = v.refine(
v.number({ integer: true }),
(value) => value % 2 === 0,
"Expected an even number",
"not_even"
);
Validation failures return a normalized issue array:
type ValidationIssue = {
path: string;
message: string;
code?: string;
};
Example:
{
success: false,
errors: [
{ path: "$.roles[1]", message: "Expected string enum value", code: "invalid_type" }
]
}
Paths are rooted at $ and include object keys and array indexes.
| Export | Description |
|---|---|
v |
Shorthand alias for Validator. |
Validator.string(options?) |
Validate strings with trimming, length, and regex options. |
Validator.number(options?) |
Validate numbers with min, max, integer, and finite checks. |
Validator.boolean() |
Validate boolean values. |
Validator.literal(value) |
Validate one exact literal value. |
Validator.enum(values) |
Validate one value from a string literal set. |
Validator.optional(inner) |
Allow undefined. |
Validator.nullable(inner) |
Allow null. |
Validator.withDefault(inner, fallback) |
Apply a fallback when the input is undefined. |
Validator.array(inner, options?) |
Validate arrays and their items. |
Validator.tuple(shape) |
Validate a fixed-length tuple. |
Validator.object(shape, options?) |
Validate plain objects using a validator shape. |
Validator.record(valueValidator, options?) |
Validate object records and optionally validate keys. |
Validator.union(...validators) |
Validate against the first matching validator. |
Validator.transform(base, mapper, message?, code?) |
Map validated input into a new output type. |
Validator.refine(base, predicate, message, code?) |
Add a custom predicate to an existing validator. |
Validator.safeParse(validator, value) |
Return `{ success, data |
Validator.safeParseFormData(validator, value) |
Convert FormData to an object and return `{ success, data |
Validator.parse(validator, value) |
Return parsed data or throw SchemaValidationError. |
Validator.parseFormData(validator, value) |
Convert FormData to an object and return parsed data or throw. |
ok(data) |
Build a success result manually. |
fail(message, path, code?) |
Build a failure result manually. |
runValidation(validator, value, path?) |
Execute a validator directly. |
isFailure(result) |
Type guard for failed validation results. |
The package is verified with:
tscvitestnpm run lint
npm test
npm run test:coverage
npm run build
npm run verify
lint, test:coverage, and build green before publishing.unknownKeys for object unknown-key handling.Validator.withDefault() for fallback values.Validator.tuple() returns a mutable tuple type in TypeScript for easier downstream use.