valrs

Getting Started

Install and configure valrs in your project

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: 'alice@example.com',
  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