

Discover more from All Things Typescript
Using Zod Schemas as Source of Truth for Typescript Types
Learn how to avoid duplicating both Zod Schemas and Typescript Types and instead infer Types from Zod Schemas, using them as a source of truth for your Types.
Hey, Wycliffe here, and welcome to this week’s mid-week scoop.
At the beginning of every week, I usually send out an article covering various Techniques and Lessons in Typescript to help you learn and build more Type-safe code in Typescript, such as How to Overload Functions in Typescript.
Please consider subscribing to this free newsletter if you haven’t, and share it with friends if you already have.
So, without further ado:
When building apps using Typescript, runtime validation is usually an afterthought. Typescript types are removed during the transpilation process, and while they help us with static type checking, it doesn’t help us with dynamic data, such as data from APIs, Forms, etc.
This is where schema validation libraries, such as Zod, Yup, etc., come in. They allow us to validate our data at runtime and ensure that we plug another possible source of bugs in our system.
Unfortunately, for most of these libraries, it means that we have defined our types twice, which can be a maintenance nightmare, as a change in the shape of our data means that both of our types (Typescript and Schema validation types) have to be updated.
What if we could create one and automatically get the other i.e. write a JSON schema validation and then automatically get Typescript Types? And this is where Zod comes in.
Zod is a TypeScript-first schema declaration and validation library. I'm using the term "schema" to broadly refer to any data type, from a simple string
to a complex nested object.
With Zod, we can define our schema, which could be any data type, and then we can get Typescript types from the schema, allowing us to use it as the source of truth. If you are interested in why you use Zod, please check out this previous issue I did a while back.
Let’s see a few examples. We will start with a string and then build our examples from there. In Zod, we can define a runtime validation of a string as followings:
import { z } from 'zod';
// we are also constraining to have a min and max size
const schema = z.string().min(3).max(10);
With the above example, we can get our type as follows:
type Schema = z.infer<typeof schema>;
And just like that, we get a type we can use within our Typescript code, inside an argument, variable definitions, etc.
Let’s move on to another more realistic example you will probably encounter more often than not. Let’s define a schema
validation object for a theoretical user that has the following fields: id
, name
, email
, password
, role
, phone
, where the phone is optional, and the role is an enum.
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
password: z.string().min(8),
role: z.enum(['admin', 'user']),
phone: z.string().optional(),
});
And now, we can get our types, just like we previously did.
type User = z.infer<typeof userSchema>;
And this is the type we get back:
Sweet, isn’t it?
Manipulating Schemas and Types
On top of defining schemas, we can manipulate existing Zod schemas using methods similar to Typescript Utility Types - such as Omit
, Pick
, etc. For instance, from the above user schema, we can drop the id field if, for instance, we are talking about.
const userNewSchema = userSchema.omit({ id: true });
type UserNew = z.infer<typeof userNewSchema>;
And in return, we get the following Typescript type:
On top of omit, Zod provides the following Typescript Utility Type inspired methods:
and more.
For more information, check out the official docs here.
Conclusion
In this issue, we learned that we could use Zod as the source of truth for our types and get types back. We learned how to use Zod schema to get both runtime validation schemas and types for Typescript for static Typing. This should help us write fewer lines of code, make it easy to maintain our Types and sync our types and schema, and get more Type safety, leading to fewer lines of code.
And that’s it from me; I hope you enjoyed ❤️ this issue as much as I did writing it, and I hope you have a fantastic week ahead.
Please consider doing one of the following things if you love this issue.
1. Share it ❤️ - Help me spread word of mouth and help All Things Typescript grow:
2. ✉️ Subscribe to this newsletter. You will receive weekly content like this from All Things Typescript that will help you learn and master Typescript.
3. Consider Sponsoring me on GitHub ❤️; you will be helping me create more free content for the benefit of everyone.
Au revoir 👋🏿.