Schema First Typescript Types Design with Valibot
Learn more about Valibot and how we can use it to design Shema's that we can infer static Types and improve developer experience by reducing code duplication and getting runtime data validation.
Let’s start with a simple hypothesis. Let’s say you have a form for your users, for instance, a sign-in form. You need a validation schema and a Typescript type, representing the shape of the form data.
Why a validation schema, you may ask? This is because data coming from untrusted sources should always be validated, for instance, form data, ensuring that you are working with data that is accurate, complete, and clean. Another example of this is data from APIs among other sources.
Back to my hypothesis, this means you will need to write about two different pieces of code that basically say the same thing - describe the shape of the data for the form. And as you know about Typescript types, they don’t exist at runtime, so you will definitely need a validation schema for runtime purposes.
What if, we could write one and automatically have the other inferred? And this is where tools like Zod and Valibot, among others, come in. With Valibot, we can define a Valibot schema for our data and then use Valibot to infer a static type for Typescript.
Let's look at a simple example. For instance, let’s say, we are dealing with a simple sign-in form, with 2 fields - an email and a password.
This is what a Valibot schema looks like:
With the above schema, we can infer static types for Typescript, which we can use within our application in places like function params Types, as shown below:
And here are the results, a Typescript static type:
As you can see from the above example, we just wrote a simple Valibot schema for our sign-in form and we got back static types for Typescript. Isn’t that cool?
So, what’s Valibot
Now that very long intro is out of the way, let's look at what Valibot is.
According to Valibot (And yes, the docs are written in the first person):
In simple terms, it’s a runtime schema validation tool, which you can use to validate data coming from unknown/untrusted sources, i.e., forms, files, and APIs. Once data is parsed by Valibot, Typescript can infer the static type automatically.
For instance, continuing by our previous example above, we can parse data, as shown below:
Please note, in the case of the above example, since data is an empty object, it would fail validation and throw an error, so we would need to catch the error and process it.
And just like magic (not magic, but the powers of Valibot), our
result variable now has a correctly inferred type, as shown below:
And the best part is, the validation will work during runtime, so, no matter the data source, we can consume it within our application, knowing that it’s what we want it to be.
Benefits of Using Valibot
The first and most obvious for those who have read this issue so far (Thank you by the way ❤️), is static type inference from our Valibot schemas. We don’t need to write duplicate pieces of code to describe the same data - a Typescript type and a validation schema.
Once we write a Valibot schema, we can get Typescript static types that we can use within our application, and upon parsing the data, Typescript can infer the correct Type.
This is huge for developer experience, as it reduces the amount of duplication within our application and now we only have to update our schema and our Types will be up to date.
This is by far for me, the most attractive proposition for Valibot and other tools like it, that do the same.
On top of the above, you can validate everything from complex objects to simple strings and numbers, etc, as shown below:
Impressed yet? For more information, check out the official docs here.
Lastly, it’s very lightweight, measuring about 1.4KBs to validate a simple login form, which means it’s not expensive to download and consume in browser environments. This is made possible by Valibot modular design, which enables tree-shaking and removal of any unused code during the bundling process.
Examples of Using Valibot
Now, that we have looked at what Valibot is and why you would want to use it, let’s take a look at a few examples:
Valibot provides several ways to parse data, the default way throws an error if validation fails, which you need to catch and do something about it.
On top of the above option, Valibot provides a
safeParse method, that instead of throwing an error, will return an object containing output (the validated data) and issues.
You can learn more about Valibot issues here.
Finally, Valibot also provides a Type Guard option, allowing you to check if data parses a validation check, which returns true or false and can be used for Type Narrowing as shown below:
Valibot supports all the above primitives:
null or both are valid values, when a value isn’t required. So, Valibot provides you with functions to make this possible.
And finally, you can customize error messages, so that when validation fails, you can provide nice messages for your users, so that it’s easy to know and understand what went wrong.
In this All Things Typescript issue, we looked at Valibot - a runtime schema validation library that can be used to validate data at runtime and ensure the integrity of data flowing within our application.
On top of that, we looked at how we can utilize it in Typescript so that we can avoid code duplication, and get static types from the Valibot schemas we create. This has the overall effect of improving the developer experience we enjoy without sacrificing much, apart from maybe learning some newer syntax - the Valibot Way of doing things.
If you would like to learn more about Valibot, check out the official documentation here.
I hope you learned something from today’s issues and I am open to suggestions for the next topics to cover for All Things Typescript. If you have any suggestions, feel free to drop them in the comment section below.
Otherwise, until next time, keep on learning.