Let’s meet Zod: TypeScript-first schema validation

A short journey about what I have learned about Zod a TypeScript-first schema validation with static type inference.

A thumbnail of the blog

Validate An Unknown Data

We know if we still use JavaScript as our main code, we cannot make sure the parameters of our function it’s a data that we need or not. The same like in TypeScript too, if we write a function that accepts arguments with type unknown, we need to make sure the parameters or data that we will proceed on our function.

import { z } from 'zod'

function submitAddressUser(address: unknown) {
const getAddress = z.string();
getAddress.parse(address)

return address
}
it('should run our app', () => {
const testIt = submitAddressUser(123412)
expect(testIt).toBe('cool address')
})
An error in the function “submitAddressUser”

A Simple User Story

Previously, we already write a simple Zod API which is string but in that API there’s also a lot of functionality that we can use, for example:

import { z } from 'zod'

function testTheApp(firstName: string) {
const getTheFirstName = z.string().min(3);
getTheFirstName.parse(firstName)

return firstName
}
it('should run our app', () => {
const getMyFirstName = testTheApp("Ad")
expect(getMyFirstName).toBe('Ad')
})
An error if does not meet a minimal character of the first name

Mix it With Rest API

The most interesting in this part, we can catch it even on the runtime let’s assume that we call a Rest API with TypeScript and normal fetch.

import { z } from 'zod'

async function getDetailTodo() {
const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => json)

console.log(fetchData)
}
A type from a fetch that returned unknown data.
A type from a fetch that returned unknown data.
import { z } from 'zod'

async function getDetailTodo() {
const validateResponse = z.object({
title: z.string()
})

const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => json)

const data = validateResponse.parse(fetchData)
return data
}
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
a return from our schema
import { z } from 'zod'

async function getDetailTodo() {
const validateResponse = z.object({
title: z.string(),
subTitle: z.string()
})

const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => json)

const data = validateResponse.parse(fetchData)
return data
}
An error from Zod that the field subTitle does not exist

Infer The Type

We already see the power from Zod with API object which is it can be like a schema validation to make sure what we need it’s what we got too. But, let’s say back to the previous topic we use this function

import { z } from 'zod'

async function getDetailTodo() {
const validateResponse = z.object({
title: z.string(),
})

const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => json)

const data = validateResponse.parse(fetchData)
return data
}
import { z } from 'zod'

const validateResponse = z.object({
title: z.string(),
})

export type DetailTodoData = Promise<z.infer<typeof validateResponse>>

async function getDetailTodo(): DetailTodoData {
const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => json)

const data = validateResponse.parse(fetchData)
return data
}

Array

In Zod, we also have an API that we can interact with the data type Array — For example, we have this function

async function getListTodo() {
const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => json)

return fetchData
}
[
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
},
{
"userId": 1,
"id": 2,
"title": "quis ut nam facilis et officia qui",
"completed": false
},
...
]
import { z } from 'zod'

async function getListTodo() {
const validateTheResponse = z.array(z.object({
title: z.string()
}))

const fetchData = await fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => json)

const data = validateTheResponse.parse(fetchData)
return data
}
Result after we create a schema of API list todo

Default Value and Optional Schema

We know on TypeScript we can also make our type optional then within the function we can give a default value.

function generateHelloThere(name?: string) {
const getName = name || 'There,'

return `Hello ${getName}`
}
it('should return a hello name', () => {
const welcomeMessage = generateHelloThere()
expect(welcomeMessage).toBe('Hello There,')
})
A green test case of function generateHelloThere
import { z } from 'zod'

function generateHelloThere(name?: string) {
const schemaName = z.string().optional().default('There,')
const getName = schemaName.parse(name)

return `Hello ${getName}`
}
A green test case of function genereateHelloThere with Zod API
An error if we are wrong to put the .default() API from Zod

The Cons

After we talked about the API that was provided by Zod, this package also has cons from my point of view. When we check the bundle size of this package from bundlejs site

A result of the bundle size of package Zod

Conclusion

Zod, it’s totally game-changer if we need to make sure our type on our function will catch it too on the runtime, but as we mentioned above there’s a cons to this package, but if you don’t think about the bundle size of your app, I think you can consider shipping this package to help to you as a guardian too on the runtime.

--

--

Currently doing some stuff on Web Platform and learn managing a team.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Adib Firman

Currently doing some stuff on Web Platform and learn managing a team.