Skip to main content
Version: 6.x

Getting Started

Vest is a validation library for forms and other flows that change over time.

It can validate only the field or step that changed, keep the results from earlier runs, and ignore an old async response when a newer one has already finished.

If you know Jest or Mocha, the authoring model will feel familiar: define a suite of named tests and use assertions to express the rules. The test-like syntax makes Vest approachable; its persistent validation runtime is what makes it different.

The problem Vest solves​

Forms are rarely validated just once. While someone fills one out:

  1. The user changes one field.
  2. Only the related rules should run.
  3. Results for other fields should remain available.
  4. Dependent fields may need to be reconsidered.
  5. Async responses may arrive in the wrong order.
  6. The complete workflow still needs one reliable validation result.

Vest handles the validation state without taking over your values, DOM, or components.

Installation​

npm i vest

Create a suite​

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

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

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

test('username', 'Username is already taken', async ({ signal }) => {
const response = await checkUsername(data.username, { signal });
enforce(response.available).isTruthy();
});
});

The suite is independent from React, Vue, Svelte, Angular, or any other UI layer. It contains the validation contract; your feature decides when to run it and how to render the result.

Run only what changed​

const result = signupSuite.only('username').run(formData);

result.isPending('username');
result.hasErrors('username');
result.getError('username');

suite.only('username') runs only username tests. Results previously established for email and other fields remain in the suite, so result.isValid() still represents the complete validation picture.

Run the full suite before submission:

const result = await signupSuite.run(formData);

if (result.isValid()) {
submit(formData);
}

When async tests exist, the returned result exposes synchronous selectors immediately and can also be awaited for final completion.

Interactive example​

This example connects a stateful Vest suite to a React form. The suite itself is framework-independent.

Loading Editor...

Test the suite without a UI​

Use runStatic() when a test should not inherit state from an earlier case:

const passwordSuite = create(data => {
test('password', 'Password must contain at least 8 characters', () => {
enforce(data.password).longerThanOrEquals(8);
});
});

const invalid = passwordSuite.runStatic({ password: 'short' });
expect(invalid.hasErrors('password')).toBe(true);

The test exercises the same rules as the UI without rendering a component. Interactive application code should still use stateful run() so focused results can accumulate over time.

When Vest is useful​

Use Vest when validation behavior includes:

  • async username, email, eligibility, inventory, or coupon checks;
  • multi-step onboarding and wizards;
  • fields that depend on other fields;
  • optional or conditional sections;
  • dynamic lists of travelers, products, or addresses;
  • errors, warnings, pending states, and progressive completion;
  • validation shared between browser and server.

If you only need to parse an API payload once, an Enforce schema's .parse() method may be enough. Attach the same schema to a Vest suite when you also need validation while the user works through a form. You can use Zod or another schema library at the boundary instead if that is already part of your stack.

Next steps​