Learn "Zod" In 5 Minutes
Tags: webdev, beginners, tutorial, typescript
Published: 2023-04-28 21:22:12

## Goals of Zod
1. Validation library (Schema first)
2. **First class** **typescript** support (No need to write types twice)
3. Immutable (Functional programming)
4. Super small library (8kb)

---

## Setup

> Can be used with Node/Deno/Bun/Any Browser etc.

`npm i zod`
`import { z } from "zod";`
Must have `strict: true` in tsconfig file

---

## Basic Usage

```ts
// creating a schema
const UserSchema = z.object({
  username: z.string(),
});

// extract the inferred type
type User = z.infer<typeof UserSchema>;
// { username: string }

const user: User = {username: "Arafat"}

// parsing
UserSchema.parse(user); // => {username: "Arafat"}
UserSchema.parse(12); // => throws ZodError

// "safe" parsing (doesn't throw error if validation fails)
UserSchema.safeParse(user); 
// => { success: true; data: {username: "Arafat"} }

UserSchema.safeParse(12); 
// => { success: false; error: ZodError }
```

---

## Basic Types

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

// primitive values
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();
```

---

## Validations
All types in **Zod** have an optional **options** parameter you can pass as the last param which defines things like error messages.

Also many types has validations you can chain onto the end of the type like optional

`z.string().optional()`
`z.number().lt(5)`
`optional()` - Makes field optional
`nullable` - Makes field also able to be `null`
`nullish` - Makes field able to be `null` or `undefined`

> Some of the handful string-specific validations

```ts
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().email();
z.string().url();
z.string().uuid();
z.string().cuid();
z.string().regex(regex);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().trim(); // trim whitespace
z.string().datetime(); // defaults to UTC, see below for options
```

> Some of the handful number-specific validations

```ts
z.number().gt(5);
z.number().gte(5); // alias .min(5)
z.number().lt(5);
z.number().lte(5); // alias .max(5)

z.number().int(); // value must be an integer

z.number().positive(); //     > 0
z.number().nonnegative(); //  >= 0
z.number().negative(); //     < 0
z.number().nonpositive(); //  <= 0

z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5)

z.number().finite(); // value must be finite, not Infinity or -Infinity
```

---

## Default Values
Can take a value or function.
Only returns a **default** when input is **undefined**.

`z.string().default("Arafat")`
`z.string().default(Math.random)`

---

## Literals

```ts
const one = z.literal("one");

// retrieve literal value
one.value; // "one"

// Currently there is no support for Date literals in Zod.
```

---

## Enums

> ### Zod Enums

> ```ts
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);

> type FishEnum = z.infer<typeof FishEnum>;
> // 'Salmon' | 'Tuna' | 'Trout'

> // Doesn't work without `as const` since it has to be read only
> const VALUES = ["Salmon", "Tuna", "Trout"] as const;
> const fishEnum = z.enum(VALUES);

> fishEnum.enum.Salmon; // => autocompletes
> ```

> ### TS Enums: (You should use Zod enums when possible)

> ```ts
enum Fruits {
  Apple,
  Banana,
}
const FruitEnum = z.nativeEnum(Fruits);
```

---

## Objects

`z.object({})`

```ts
// all properties are required by default
const Dog = z.object({
  name: z.string(),
  age: z.number(),
});

// extract the inferred type like this
type Dog = z.infer<typeof Dog>;

// equivalent to:
type Dog = {
  name: string;
  age: number;
};
```

`.shape.key` - Gets schema of that key

```ts
Dog.shape.name; // => string schema
Dog.shape.age; // => number schema
```

`.extend` - Add new fields to schema

```ts
const DogWithBreed = Dog.extend({
  breed: z.string(),
});
```

`.merge` - Combine two object schemas

```ts
const BaseTeacher = z.object({ students: z.array(z.string()) });
const HasID = z.object({ id: z.string() });

const Teacher = BaseTeacher.merge(HasID);
type Teacher = z.infer<typeof Teacher>; // => { students: string[], id: string }
```

`.pick/.omit/.partial` - Same as TS

```ts
const Recipe = z.object({
  id: z.string(),
  name: z.string(),
  ingredients: z.array(z.string()),
});


// To only keep certain keys, use .pick
const JustTheName = Recipe.pick({ name: true });
type JustTheName = z.infer<typeof JustTheName>;
// => { name: string }


// To remove certain keys, use .omit
const NoIDRecipe = Recipe.omit({ id: true });
type NoIDRecipe = z.infer<typeof NoIDRecipe>;
// => { name: string, ingredients: string[] }


// To make every key optional, use .partial
type partialRecipe = Recipe.partial();
// { id?: string | undefined; name?: string | undefined; ingredients?: string[] | undefined }
```

`.deepPartial` - Same as partial but for nested objects

```ts
const user = z.object({
  username: z.string(),
  location: z.object({
    latitude: z.number(),
    longitude: z.number(),
  }),
  strings: z.array(z.object({ value: z.string() })),
});

const deepPartialUser = user.deepPartial();

/*
{
  username?: string | undefined,
  location?: {
    latitude?: number | undefined;
    longitude?: number | undefined;
  } | undefined,
  strings?: { value?: string}[]
}
*/
```

`passThrough` - Let through non-defined fields

```ts
const person = z.object({
  name: z.string(),
});

person.parse({
  name: "bob dylan",
  extraKey: 61,
});
// => { name: "bob dylan" }
// extraKey has been stripped


// Instead, if you want to pass through unknown keys, use .passthrough()
person.passthrough().parse({
  name: "bob dylan",
  extraKey: 61,
});
// => { name: "bob dylan", extraKey: 61 }
```

`.strict` - Fail for non-defined fields

```ts
const person = z
  .object({
    name: z.string(),
  })
  .strict();

person.parse({
  name: "bob dylan",
  extraKey: 61,
});
// => throws ZodError
```

---

## Arrays
`const stringArray = z.array(z.string());` - Array of strings

`.element` - Get schema of array element

```ts
stringArray.element; // => string schema
```

`.nonempty` - Ensure array has a value

```ts
const nonEmptyStrings = z.string().array().nonempty();
// the inferred type is now
// [string, ...string[]]

nonEmptyStrings.parse([]); // throws: "Array cannot be empty"
nonEmptyStrings.parse(["Ariana Grande"]); // passes
```

`.min/.max/.length` - Gurantee certail size

```ts
z.string().array().min(5); // must contain 5 or more items
z.string().array().max(5); // must contain 5 or fewer items
z.string().array().length(5); // must contain 5 items exactly
```

---

> ## Advanced Types

> > ### Tuple

> > Fixed length array with specific values for each index in the array

> > Think for example an array of coordinates.

> > `z.tuple([z.number(), z.number(), z.number().optional()])`

> > `.rest` - Allow infinite number of additional elements of specific type

> > ```ts
const variadicTuple = z.tuple([z.string()]).rest(z.number());
const result = variadicTuple.parse(["hello", 1, 2, 3]);
// => [string, ...number[]];
```

> > ---

> > ### Union
> > Can be combined with things like arrays to make very powerful type checking. 

> > ```ts
let stringOrNumber = z.union([z.string(), z.number()]);
// same as
let stringOrNumber = z.string().or(z.number());

> > stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes
```

> > > #### Discriminated unions
> > > Used when one key is shared between many types.

> > > Useful with things like statuses.

> > > Helps Zod be more performant in its checks and provides better error messages

> > > ```ts
const myUnion = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  z.object({ status: z.literal("failed"), error: z.instanceof(Error) }),
]);

> > > myUnion.parse({ status: "success", data: "yippie ki yay" });
> > > ```

---

## Records
Useful when you don't know the exact keys and only care about the values

`z.record(z.number())` - Will gurantee that all the values are numbers

`z.record(z.string(), z.object({ name: z.string() }))` - Validates the keys match the pattern and values match the pattern. Good for things like stores, maps and caches.

---

## Maps
Usually want to use this instead of key version of record

```ts
const stringNumberMap = z.map(z.string(), z.number());

type StringNumberMap = z.infer<typeof stringNumberMap>;
// type StringNumberMap = Map<string, number>
```

---

## Sets
Works just like arrays (Only unique values are accepted in a set)

```ts
const numberSet = z.set(z.number());
type NumberSet = z.infer<typeof numberSet>;
// type NumberSet = Set<number>
```

---

## Promises
Does validation in two steps:

1. Ensures object is promise
2. Hooks up `.then` listener to the promise to validate return type.

```ts
const numberPromise = z.promise(z.number());

numberPromise.parse("tuna");
// ZodError: Non-Promise type: string

numberPromise.parse(Promise.resolve("tuna"));
// => Promise<number>

const test = async () => {
  await numberPromise.parse(Promise.resolve("tuna"));
  // ZodError: Non-number type: string

  await numberPromise.parse(Promise.resolve(3.14));
  // => 3.14
};
```

---

## Advanced Validation

`.refine`

```ts
const email = z.string().refine((val) => val.endsWith("@gmail.com"),
{message: "Email must end with @gmail.com"}
)
```
Also you can use the `superRefine` method to get low level on custom validation, but most likely won't need it.

### Handling Errors
Errors are extremely detailed in Zod and not really human readable out of the box. To get around this you can either have custorm error messages for all your validations, or you can use a library like `zod-validation-error` which adds a simple `fromZodError` method to make error human readable.

```ts
import { fromZodError } from "zod-validation-error"

console.log(fromZodError(results.error))
```

---

## Conclusion
There are many more concepts of Zod, and I can't explain all that stuff here. However, If you want to discover them, head to [Zod's official documentation](https://zod.dev/?id=error-handling). They've explained everything perfectly there. 

So, this was it, guys. I hope you guys will like this crash course. I've tried my best to pick all of the essential concepts of Zod and explain them. If you have any doubts or questions, then feel free to ask them in the comment section. I will answer as soon as I see it. See you all in my next article😊.

**Visit**:
👨‍💻[My Portfolio](https://portfolio-khaki-iota-89.vercel.app)
🏞️[My Fiverr](https://www.fiverr.com/sunnyislam990)
🌉[My Github](https://github.com/arafat4693)
🧙‍♂️[My LinkedIn](https://www.linkedin.com/in/arafat-freelancer)