Custom Schema Validation in TypeScript with Zod
Tags: javascript, typescript, zod, node
Published: 2023-01-31 13:59:31

[Zod](https://zod.dev/) 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.

```shell
mkdir zod-custom-validation
cd zod-custom-validation
npm init -y
npm i zod
npm i tsx --save-dev
```

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"
  }
```

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
```

## 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

```typescript
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))
```
 
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. 

```typescript
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))
```

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](https://zod.dev/). 

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!