# ZOD - TypeScript-first schema data validation

Do you guys know that Typescript only does type-checking at compile time and no runtime checks?

Well, why do we even need runtime checks after typescript compile time checks, well the first use could be validating data on the server side, which typescript has no idea of at compile time.

we can't always be sure about what type of variables we'll get from external sources like APIs or form inputs.

For example, when we have some form to be filled, Typescript can't guarantee the user-filled inputs to be 100% type correct as we want on our database.

# What is Zod and why do we need it?

Zod is a tool that solves this problem. It fills this TypeScript blindspot and helps with type safety during runtime by having an additional runtime check.


> Zod is a TypeScript-first schema declaration and validation library. It provides safety checks at runtime working in conjunction with Typescript!

# Installation

Zod can be installed into your react application using npm or yarn

``` npm install zod ```

``` yarn add zod ```

### Requirements

- TypeScript 4.5+!
- ```strict``` mode must be enabled in your ```tsconfig.json``` file
```
// tsconfig.json
{
    "compilerOptions": {
      "strict": true
    }
}
```

# Usage

Let's string by creating a very simple string schema using zod

```
import { z } from "zod";

const stringSchema = z.string().min(10).max(15);

stringSchema.parse("example string")

```

This will be safely parsed as it follows the zod schema we used to parse it.

But if we try something that outflows the schema parsing instructions, such as 
```
stringSchema.parse("string of much greater length")
```
This will throw an error

```
{
      code: 'too_big',
      maximum: 15,
      type: 'string',
      inclusive: true,
      message: 'String must contain at most 15 characters,
      path: []
    }
```

Zod also provides various other methods such as 
```
z.string();
z.number();
z.bigint();
z.boolean();
z.date();
z.symbol();

// empty types
z.undefined();
z.null();
z.void(); // accepts undefined

// catch-all types
// allows any value
z.any();
z.unknown();

// never type
// allows no values
z.never();
```

# Objects in zod

Objects can be used extensively whether they be validating a user form or fetching data from an API

Let's take an example for validating a user form data

```
const userData = z.object({
  firstName: z.string().min(1).max(10),
  lastName: z.string().min(1).max(10),
  email: z.string().email(),
})
```


# zod to parse API data

In this example let's fetch data from the random user API and validate it using zod

```
const randomUsers = async () => {
  const data = await fetch("https://swapi.dev/api/people/")
    .then((res) => res.json(),
  );
};
```
Now this would return an array of users, we can parse the result using the `z.array()` method

```
  const randomUsers = z.object({
  results: z.array(z.object()),
});

  const parsedData = randomUsers.parse(data);

  return parsedData.results;
```
Here we are declaring that the `results` array could be of any object type, we can further specify the data inside the result array to be of the `user` type like this

```
  const randomUser = z.object({
      name: z.object({
        first: z.string(),
        last: z.string()
      })
  })

// declare the type of results array to be randomUser

  const randomUsers = z.object({
  results: z.array(randomUser),
});

  const parsedData = randomUsers.parse(data);

  return parsedData.results;
```

# Make schema values optional

What if we want our email input to be optional here, we can do it as such by adding `.optional()` to the end of our email schema: 
```
const userData = z.object({
  firstName: z.string().min(1).max(10),
  lastName: z.string().min(1).max(10),
  email: z.string().email().optional(),
})
```

# Default value in the schema

What if we want `email` to be optional but don't want it to be undefined in our database, we can do so by passing a default value in its place by adding `.default()` to the end of our email schema: 

```
const userData = z.object({
  firstName: z.string().min(1).max(10),
  lastName: z.string().min(1).max(10),
  email: z.string().email().default("default@gmail.com"),
})

```

# Reducing Duplicate values by composing schemas

Suppose we have this schema with `User`, `Post`, `Comment`

```
const User = z.object({
  id: z.string().uuid(),
  name: z.string(),
});

const Post = z.object({
  id: z.string().uuid(),
  title: z.string(),
});

const Comment = z.object({
  id: z.string().uuid(),
  comment: z.string(),
});
```
Notice that `id` is repeated in every schema

A very basic solution could be placing id in its own type and other schemas can use that reference.

```
const Id = z.string().uuid();

const User = z.object({
  id: Id,
  name: z.string(),
});

const Post = z.object({
  id: Id,
  title: z.string(),
  body: z.string(),
});

const Comment = z.object({
  id: Id,
  text: z.string(),
});
```

But this is still quite repetitive, Another solution can be by using the `extend` method.

We can create a base object schema with id and then extend all other schema and add to the base schema as such: 
```
const schemaWithId = z.object({
  id: z.string().uuid()
});

const User = ObjectWithId.extend({
  name: z.string(),
});

const Post = ObjectWithId.extend({
  title: z.string(),
});

const Comment = ObjectWithId.extend({
  comment: z.string(),
});
```

now we can add multiple values directly to share between all the objects

```
const schemaWithId = z.object({
  id: z.string().uuid(),
  createdAt: z.datetime()
});

```

Both `id` and `createdAt` values will be shared between all the objects.


# Transform Data within the schema

Let's take back the random user but with a slight change that we are receiving `fullName` instead of separate `firstName` and `lastName` but and we want to split the `fullName` value into `firstName` and `lastName`

This can be done by transforming the `fullName` value by splitting it at the ` " " ` blank space within the schema by using the `transform` method

```
const userData = z.object({
  fullName: z.string()
})
  .transform(user => ({
      ...user,
      nameAsArray: user.name.split( " " ),  
  }));

``` 

the `transform` method can be used in various ways to transform that data within the schema.


# Type Inference

Suppose we already have defined our schema somewhere and want to use the same schema definition somewhere else without rewriting the schema again, we can do such by extracting its typescript type.

We can extract the TypeScript type of any schema with `z.infer<typeof schema>` 


```

const user = z.object({
  name: z.string(),
  age: z.number(),
});

type userType = z.infer<typeof user>;

```

zod is a very easy library to implement and having both compile time and runtime checks will even boost your application's performance.


I hope these help you to get a basic understanding of how zod works. zod is much more than just this, to learn and understand much more about zod, use these official [docs](https://github.com/colinhacks/zod)  


Thanks for reading till the last

Reach me on [Twitter](https://twitter.com/Aadarsh805)
