Custom Schema Validation in TypeScript with Zod

isnan__h

ishan

Posted on January 31, 2023

Custom Schema Validation in TypeScript with Zod

Zod is a schema validation library. Its goes well with building Typescript projects. Zod has zero dependencies which means that you can install and use Zod without any other libraries, and it will help you keep your bundle size less bulky.

In this article we will cover how can you use a basic to custom validation logic for your schema parsing validation using Zod. This will just cover some basic way to add a custom validation logic in your schema created.

Why need a schema validation?

Schema validation is the process of verifying the data structure to a pre defined set of schema specification. We can use it to ensure the validity of input data, as well as to document and enforce your application’s data structures.

We might need to add a parsing library to our application mainely for the reasons below

  • Parse form data before sending to the server
  • Parse data before sending back out to the client
  • Parse data in functions around our application
  • Parse data at the API boundary before sending to the DB
  • Parse data from an third-party data source

Zod makes it extremely easy to create simple schemas to validate primitives, objects, type inference from the schemas, composing schemas and more.

Installing

We will start a barebones project with the following commands.

mkdir zod-custom-validation
cd zod-custom-validation
npm init -y
npm i zod
npm i tsx --save-dev
Enter fullscreen mode Exit fullscreen mode

After we have install all the needful packages to get start with typescript compilation we need to tweak this to our package.json file

  "scripts": {
    "validation": "tsx watch validation.ts"
  }
Enter fullscreen mode Exit fullscreen mode

Here we specify we will be running a file name validation.ts which we will be creating to experiment with zod validation.

Let's create a validation.ts file in our root folder with commands below

touch validaton.ts
npm run validaton
Enter fullscreen mode Exit fullscreen mode

Basic Validation

To start with a basic validation we could start with having a User fields. This would something like

  • id would be a type of number

  • name would be a string

  • portfolio would be a string and should be a url

  • salary would be a number and is nullable/optional

import z from 'zod';

const schema = z.object({
  id: z.string({
    required_error: "ID is required",
    invalid_type_error: "ID must be a string"
  }),
  name: z.string({
    required_error: "Name is required",
    invalid_type_error: "Name must be a string"
  }),
  portfolio: z.string({
    required_error: "portfolio is required",
    invalid_type_error: "portfolio must be a string"
  }).url(),
  salary: z.number({
    invalid_type_error: "salary must be a number"
  }).optional(),
})

type ValidatePayload = z.infer<typeof schema>

const payload: ValidatePayload = {
  id: "35625",
  name: "New user",
  portfolio: "https://dev.to/isnan__h/custom-schema-validation-in-typescript-with-zod-5cp5"
}

const parseSchema = (props: ValidatePayload) => {
  return schema.parse(props)
}

const result = parseSchema(payload)
console.log(JSON.stringify(result))
Enter fullscreen mode Exit fullscreen mode

In the above snippet we defined a schema with a set of rules. These rules must me passed to execute our validation error free.

To check this we can pass a url that is not a URL then it would fail with a error needs portfolio to be a URL.

Custom Validation

To add a custom validation can be achieved with method provided to us .superRefine() and we have the .refine() method, The refine method is used to provide custom validation logic via refinements.

const schema = z.object({
  name: z.string({
    required_error: "Name is required",
    invalid_type_error: "Name must be a string"
  }),
  phone: z.number({
    required_error: "Phone number is required",
    invalid_type_error: "Phone must be a number"
  }),
  confirmPhone: z.number({})
}) .refine((data) => data.phone === data.confirmPhone, {
  message: "Phone numbers dont match"
});

const payload: ValidatePayload = {
  name: "New user",
  phone: 24562454453465,
  confirmPhone: 24562454453465
}

const result = parseSchema(payload)
console.log(JSON.stringify(result))
Enter fullscreen mode Exit fullscreen mode

This is a basic implementation of how we can add custom logic using the refine() method. In the above example we are making sure the phone and confirmPhone needs to be both same.

Conclusion

We could make the use of Zod to verify the schema we define in our application. These primitives can be chained together to build a very robust and flexible schema design for your application.

Having a runtime validation is always a needed thing for our application, as it makes our code more readable and sanitizes user inputs before redirecting them to the server.

For more information on Zod, be sure to check out the excellent documentation on their site.

You can find there more detailed examples of how to use all of the functionality that Zod has to offer.

https://zod.dev/?id=table-of-contents
https://zod.dev/?id=error-handling

Happy coding!

💖 💪 🙅 🚩
isnan__h
ishan

Posted on January 31, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related