valrs

Getting Started

This guide will help you install valrs and start validating data in minutes.

Installation

Install valrs using your preferred package manager:

npm install valrs
pnpm add valrs
yarn add valrs

Basic Usage

Import and Create Schemas

import { v } from 'valrs';
 
// Create a simple schema
const schema = v.string();
 
// Parse data (throws on invalid input)
const result = schema.parse('hello');
console.log(result); // 'hello'
 
// Safe parse (returns result object)
const safeResult = schema.safeParse('hello');
if (safeResult.success) {
  console.log(safeResult.data); // 'hello'
} else {
  console.log(safeResult.error); // ValError with Zod-compatible API
}

Define Object Schemas

import { v } from 'valrs';
 
const User = v.object({
  id: v.number().int().positive(),
  name: v.string().min(1).max(100),
  email: v.string().email(),
  role: v.enum(['admin', 'user', 'guest']),
  createdAt: v.date(),
});
 
// Type inference
type User = v.infer<typeof User>;
 
// Parse and validate
const user = User.parse({
  id: 1,
  name: 'Alice',
  email: '[email protected]',
  role: 'admin',
  createdAt: new Date(),
});

Handle Validation Errors

When validation fails, parse() throws a ValError with a Zod-compatible API:

import { v, ValError } from 'valrs';
 
const User = v.object({
  name: v.string().min(1),
  age: v.number().int().positive(),
});
 
try {
  User.parse({ name: '', age: -5 });
} catch (error) {
  if (error instanceof ValError) {
    // Access all validation issues
    console.log(error.issues);
    // [
    //   { code: 'too_small', path: ['name'], message: 'String must be at least 1 character(s)', ... },
    //   { code: 'too_small', path: ['age'], message: 'Number must be positive', ... }
    // ]
 
    // Format errors as nested object matching data structure
    console.log(error.format());
    // {
    //   _errors: [],
    //   name: { _errors: ['String must be at least 1 character(s)'] },
    //   age: { _errors: ['Number must be positive'] }
    // }
 
    // Flatten errors for simple form display
    console.log(error.flatten());
    // {
    //   formErrors: [],
    //   fieldErrors: {
    //     name: ['String must be at least 1 character(s)'],
    //     age: ['Number must be positive']
    //   }
    // }
  }
}

For safe parsing without exceptions:

const result = User.safeParse({ name: '', age: -5 });
 
if (!result.success) {
  // result.error is a ValError
  console.log(result.error.issues);
  console.log(result.error.format());
  console.log(result.error.flatten());
}

TypeScript Integration

valrs provides full type inference. Define your schema once and get types automatically:

import { v } from 'valrs';
 
const UserSchema = v.object({
  id: v.string().uuid(),
  email: v.string().email(),
  profile: v.object({
    name: v.string(),
    bio: v.string().optional(),
  }),
  tags: v.array(v.string()),
});
 
// Infer the type from the schema
type User = v.infer<typeof UserSchema>;
// {
//   id: string;
//   email: string;
//   profile: { name: string; bio?: string };
//   tags: string[];
// }
 
// Use the type in your code
function createUser(data: User): void {
  // data is fully typed
}
 
// Parse unknown data into typed values
const user: User = UserSchema.parse(unknownData);

Input vs Output Types

When using transforms, input and output types may differ:

const StringToNumber = v.string().transform((val) => parseInt(val, 10));
 
type Input = v.input<typeof StringToNumber>;  // string
type Output = v.output<typeof StringToNumber>; // number

Framework Integration

React

import { useState } from 'react';
import { v } from 'valrs';
 
const FormSchema = v.object({
  email: v.string().email(),
  password: v.string().min(8),
});
 
function LoginForm() {
  const [errors, setErrors] = useState<Record<string, string[]>>({});
 
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const data = Object.fromEntries(formData);
 
    const result = FormSchema.safeParse(data);
 
    if (!result.success) {
      setErrors(result.error.flatten().fieldErrors);
      return;
    }
 
    // Submit validated data
    submitLogin(result.data);
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" />
      {errors.email && <span>{errors.email[0]}</span>}
 
      <input name="password" type="password" />
      {errors.password && <span>{errors.password[0]}</span>}
 
      <button type="submit">Login</button>
    </form>
  );
}

Next.js Server Actions

'use server';
 
import { v } from 'valrs';
 
const CreatePostSchema = v.object({
  title: v.string().min(1).max(200),
  content: v.string().min(1),
  published: v.boolean().default(false),
});
 
export async function createPost(formData: FormData) {
  const data = {
    title: formData.get('title'),
    content: formData.get('content'),
    published: formData.get('published') === 'true',
  };
 
  const result = CreatePostSchema.safeParse(data);
 
  if (!result.success) {
    return { error: result.error.flatten() };
  }
 
  // Save to database
  const post = await db.post.create({ data: result.data });
  return { success: true, post };
}

Express.js Middleware

import express from 'express';
import { v, ValSchema } from 'valrs';
 
const CreateUserSchema = v.object({
  name: v.string().min(1),
  email: v.string().email(),
  age: v.number().int().min(0).max(150),
});
 
function validate<T>(schema: ValSchema<unknown, T>) {
  return (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const result = schema.safeParse(req.body);
 
    if (!result.success) {
      return res.status(400).json({
        error: 'Validation failed',
        details: result.error.flatten(),
      });
    }
 
    req.body = result.data;
    next();
  };
}
 
app.post('/users', validate(CreateUserSchema), async (req, res) => {
  // req.body is validated and typed
  const user = await createUser(req.body);
  res.json(user);
});

WASM Initialization (Optional)

For maximum performance in browsers, you can explicitly initialize the WebAssembly module:

import { v, init, isInitialized } from 'valrs';
 
// Optional: explicitly initialize WASM
await init();
 
console.log(isInitialized()); // true
 
// Now use valrs as normal
const schema = v.string();

WASM initialization is automatic in most environments. You only need to call init() explicitly if you want to control when the WASM binary is loaded.

Next Steps

  • Primitives - All primitive types and their methods
  • Objects - Object schemas and manipulation
  • Collections - Arrays, tuples, records, maps, and sets
  • Streaming - O(1) memory validation for large files

On this page