Vest Form Validation Framework

6.3.2 · active · verified Sun Apr 19

Vest is a declarative and framework-agnostic JavaScript validation framework that enables developers to write validation logic using a syntax familiar to unit testing frameworks like Jest or Mocha. It aims to separate validation concerns from UI logic, promoting cleaner components and easier testing. Currently stable at version 6.3.2, Vest offers robust features including asynchronous validation support, strong TypeScript type safety, server-side rendering (SSR) compatibility with state hydration, and extensibility for custom rules. It distinguishes itself by managing validation state intelligently, handling dependent fields, and providing a powerful assertion library (`enforce`). The project maintains a consistent release cadence with regular patch and minor updates, and significant architectural shifts between major versions. Vest is designed for complex validation scenarios across various JavaScript environments (React, Vue, Svelte, Angular, Node.js, vanilla JS) and implements the Standard Schema specification.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to define a Vest validation suite with synchronous and asynchronous tests, including conditional logic, and how to execute the suite and interpret its results for a typical signup form.

import { create, test, enforce } from 'vest';

// Simulate an async API call, e.g., checking if a username exists
const doesUsernameExist = async (username: string): Promise<boolean> => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(username === 'admin'); // 'admin' is taken
    }, 500);
  });
};

interface FormData {
  username?: string;
  email?: string;
  password?: string;
  confirmPassword?: string;
}

const signupSuite = create((data: FormData = {}) => {
  test('username', 'Username is required', () => {
    enforce(data.username).isNotBlank();
  });

  test('username', 'Username must be at least 3 characters long', () => {
    enforce(data.username).longerThanOrEquals(3);
  });

  // Async validation
  test('username', 'Username is already taken', async () => {
    if (data.username) {
      await enforce(await doesUsernameExist(data.username)).isFalsy();
    }
  });

  test('email', 'Email is required', () => {
    enforce(data.email).isNotBlank();
  });

  test('email', 'Email must be valid', () => {
    enforce(data.email).isEmail();
  });

  test('password', 'Password is required', () => {
    enforce(data.password).isNotBlank();
  });

  test('password', 'Password must be at least 8 characters long', () => {
    enforce(data.password).longerThanOrEquals(8);
  });

  // Conditional validation: only run if password is valid and present
  if (data.password && signupSuite.get().hasErrors('password') === false) {
    test('confirmPassword', 'Passwords do not match', () => {
      enforce(data.confirmPassword).equals(data.password);
    });
  }
});

async function validateForm(formData: FormData) {
  console.log('Validating with data:', formData);
  const result = await signupSuite.run(formData);

  console.log('Validation Results:');
  console.log('  isValid:', result.isValid());
  console.log('  hasErrors:', result.hasErrors());
  console.log('  getErrors:', result.getErrors());
  console.log('  getWarnings:', result.getWarnings());

  if (!result.isValid()) {
    console.log('Validation failed!');
  } else {
    console.log('Validation passed!');
  }
}

// Example usage
validateForm({ username: '', email: 'test@example.com', password: 'password123', confirmPassword: 'password123' });
validateForm({ username: 'john', email: 'john@example.com', password: 'short', confirmPassword: 'short' });
validateForm({ username: 'admin', email: 'admin@example.com', password: 'password123', confirmPassword: 'password123' });
validateForm({ username: 'newUser', email: 'new@example.com', password: 'supersecurepassword', confirmPassword: 'supersecurepassword' });

view raw JSON →