Data is king, but we must ensure the validity and accuracy of the data we are working with. As developers, we create dynamic applications that respond to the data that users provide. However, users don’t always input information in the format that we need.
For example, consider an onboarding form for new registrations. Without any checks in place, a user might enter their date of birth as a text string when the system expects an integer or date format.
One way to ensure that users can only submit data when it matches our desired format is to use schema validators. These tools help define and validate the structure of data to ensure that it is accurate and consistent.
In this article, we’ll explore Valibot, one of the latest schema declaration and validation libraries to hit the JavaScript ecosystem. We‘ll discuss how Valibot works and the benefits it provides. We’ll also walk through an example of validating a form with Valibot.
Jump ahead:
Valibot is a new JavaScript schema library for validating structural data. It was released in July of 2023, and has quickly grown in popularity, acquiring over 3,500 GitHub stars as of this writing.
Valibot stands out from other more established schema libraries like Yup and Zod because its optimized source code makes it extremely lightweight. Recently, a developer shared how they saved 40kB by switching from Zod to Valibot!
The secret behind Valibot’s lightweight nature is its modular API design which allows bundlers to use import statements to remove unneeded code. This way, only the code that we use makes it to production.
According to claims from Valibot’s website, “Valibot can reduce the bundle size up to 98% compared to Zod. This can be a big advantage, particularly for client-side form validation.”
In terms of bundle size, here’s how Valibot compares to other schema libraries:
Valibot’s features include:
Now that we understand more about Valibot, let’s see how it works in practice.
Run any of the commands below to install Valibot:
npm install valibot # npm yarn add valibot # yarn pnpm add valibot # pnpm bun add valibot # bun
Next, import the following methods into your app:
# With individual imports import { … } from 'valibot'; # With a wildcard import import * as v from 'valibot';
Valibot provides a range of schemas, methods, validations, and transformations:
number
, boolean
, string
, symbol
, object
parse
, strict
, partial
endsWith
, includes
, integer
, equal
, email
toLowerCase
, toMinValue
, toMaxValue
, toUpperCase
N.B., check out the API reference for more information on the full capabilities of Valibot’s API
Let’s play around with these functionalities by creating a simple login schema:
import { object, string } from 'valibot'; // login schema const LoginSchema = object({ email: string(), password: string(), });
We use the object
schema because the form’s data will be in an object format. To validate the data for something else, say a number, we would use the number
schema because, in that case, we would expect an integer.
Next, we describe the structure of the data we expect. In this case, we expect an email
and a password
, which are both strings. We use the string
schema to ensure that the email
and password
are actually provided in string format.
In cases where the data received does not match the format or structure that Valibot expects, it throws an error. We can customize the error message by passing an error text as an argument:
const LoginSchema = object({ email: string("email must have at least 6 characters"), password: string("password must have at least 10 characters"), });
Valibot allows us to chain multiple validations together. Let’s see how this works:
import { email, minLength, object, string } from 'valibot'; const LoginSchema = object({ email: string([ minLength(6, "email must have at least 6 characters"), email('The email address is badly formatted.'), ]), password: string([ minLength(10, "password must have at least 10 characters"), ]), });
Here, we created a pipeline, which is an array of functions that the data being validated passes through synchronously. This pipeline uses the minLength
validator to check the minimum lengths of the email and password. It also checks if the email is valid with the email
validator.
There are cases where we want to create custom validations for specific use cases. For example, let’s extend the LoginSchema
example to confirm that the password contains at least one uppercase letter, one number, and one special character:
import { custom, email, object, password, minLength, } from 'valibot'; const LoginSchema = object({ email: string(), password: string([ minLength(10, "password must have at least 10 characters"), custom( (value) => !value.match(/[A-Z]/) !== null, "Password must contain at least one uppercase letter." ), custom( (value) => value.match(/[0-9]/) !== null, "Password must contain at least one number." ), custom( (value) => !value.match(/[^a-zA-Z0-9]/), "Password must contain at least one special character." ), ]), });
Valibot provides a custom
validator, which we used to create a custom validation function. Here, we changed three custom validations together and used Regex to check if the password met the stated requirements.
For the final exploration, let’s use Valibot’s schema validation capabilities to validate a form. We’ll work with React Hook Form (RHF) and its Valibot resolver for the form validation and Modular Forms, a state management and input validation form library, that also supports Valibot.
First, set up the form:
import { useForm } from "react-hook-form"; import { valibotResolver } from "@hookform/resolvers/valibot"; export default function App() { const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: valibotResolver(LoginSchema), }); return ( <div className="container"> <form onSubmit={handleSubmit((d) => console.log(d))}> // email field <div className="form-row"> <label htmlFor="email">Email</label> <input {...register("email")} /> {errors.email && <p className="error"> {errors.email.message} </p>} </div> //password <div className="form-row"> <label htmlFor="password">Password</label> <input {...register("password")} /> {errors.password && ( <p className="error"> {errors.password.message} </p> )} </div> <button type="submit">Log In</button> </form> </div> ); }
This is a simple form that’s been hooked up to RHF. It has an email
and password
field, and is set up to display any errors that may exist in the fields upon submission.
Next, add the LoginSchema
:
import { custom, email, minLength, object, string } from "valibot"; const LoginSchema = object({ email: string([ minLength(6, "email must have at least 6 characters"), email("Invalid email format"), ]), password: string([ minLength(10, "password must have at least 10 characters"), custom( (value) => !value.match(/[A-Z]/) !== null, "Password must contain at least one uppercase letter" ), custom( (value) => value.match(/[0-9]/) !== null, "Password must contain at least one number" ), custom( (value) => !value.match(/[^a-zA-Z0-9]/), "Password must contain at least one special character" ), ]), });
The schema is the same password chain validation schema we set up earlier, with one difference: we added some chain validation for the email. The email’s validation checks that the email has at least six characters and is properly formatted.
With that, we have successfully used RHF and Valibot to validate the form. Here’s an abbreviated snippet showing how all the code looks together:
import { useForm } from "react-hook-form"; import { valibotResolver } from "@hookform/resolvers/valibot"; import { custom, email, minLength, object, string } from "valibot"; const LoginSchema = object({ email: string([...]), password: string([...]), });
Here’s our Valibot validation form in action:
Valibot is a great alternative to other lightweight schema validation libraries. Its modular API and minimal size make it a viable option for developers who want to improve the performance of their applications.
Valibot has a unique selling point, but I look forward to seeing how it performs in the long term and how many developers choose it against its more popular and established alternatives. I should point out that the library’s documentation is currently incomplete. This is understandable, as Valibot was only recently released. I look forward to seeing more comprehensive documentation, hopefully soon!
I recommend you give Valibot a try in your next project and share your experience!
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowIt’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.