Zod: TypeScript-first schema validation library
Published: 2024-01-21 21:23:03

## Introduction

Zod is a TypeScript-first schema declaration and validation library. In this blog post, we'll delve into the fascinating world of Zod, exploring its features, integration with TypeScript, and how it can revolutionize the way you approach data validation in your projects.

## Why Zod?

Zod isn't just another validation library; it's a game-changer. Imagine a world where defining and validating your code is a breeze, and you only need to do it once. Zod brings this vision to life by being TypeScript-first, seamlessly integrating with TypeScript types. No more redundant type declarations – with Zod, your schema is your type, reducing redundancy and increasing efficiency.

Zod offers a number of advantages

**Zero Dependencies**: Zod prides itself on its lightweight nature. With zero external dependencies, it ensures a streamlined development process without unnecessary baggage. Weighing in at just 8kb when minified and zipped, Zod is a powerful tool that won't weigh down your projects.

**Cross-Platform Compatibility**: Whether you're working in Node.js or crafting cutting-edge applications for modern browsers, Zod has you covered. Its versatility extends across different environments, ensuring consistent and reliable validation wherever your code runs.

**Immutable Architecture**: Zod adopts an immutable approach, a design choice that pays off in terms of clarity and reliability. Methods such as .optional() return new instances, preserving the integrity of your data while allowing for seamless chaining.

**Functional Approach**: Zod challenges the conventional 'validate' mindset by promoting a functional approach – parse, don't validate. This shift in perspective simplifies your workflow and enhances the overall robustness of your validation process.

**Plain JavaScript Compatibility**: You don't need to be a TypeScript enthusiast to benefit from Zod's capabilities. While it integrates seamlessly with TypeScript, Zod is equally adept at working with plain JavaScript. 

## Basic Usage

**Creating a simple string schema**
```
import { z } from "zod";

// creating a schema for strings
const mySchema = z.string();

// parsing
mySchema.parse("tuna"); // => "tuna"
mySchema.parse(12); // => throws ZodError

// "safe" parsing (doesn't throw error if validation fails)
mySchema.safeParse("tuna"); // => { success: true; data: "tuna" }
mySchema.safeParse(12); // => { success: false; error: ZodError }
```

`import { z } from "zod";`
This line imports Zod library into your project

Creating a Schema for Strings:
`const mySchema = z.string();`
Here, you define a schema using Zod for validating strings. z.string() creates a schema that expects the data to be a string.

Parsing with parse method:
`mySchema.parse("tuna"); // => "tuna"`
`mySchema.parse(12); // => throws ZodError
`
The parse method is used to validate and parse data according to the defined schema. The first line successfully parses the string "tuna" as it conforms to the schema. The second line attempts to parse the number 12, but it throws a ZodError since it doesn't match the expected string type.

"Safe" Parsing with safeParse method:
`mySchema.safeParse("tuna"); // => { success: true; data: "tuna" }
mySchema.safeParse(12); // => { success: false; error: ZodError }
`
The safeParse method is similar to parse, but it doesn't throw an error if the validation fails. Instead, it returns an object with a success property indicating whether the parsing was successful, along with the parsed data or an error object.

**Creating an object schema**

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

const User = z.object({
  username: z.string(),
});

User.parse({ username: "Ludwig" });

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

`const User = z.object({
  username: z.string(),
});`
Here, you define a schema named User for an object using z.object(). The object schema specifies that it should have a property named username of type string.

Parsing an Object:
`User.parse({ username: "Ludwig" });
`The parse method is used to validate and parse an object according to the defined schema. In this case, it successfully parses an object with the property username set to the string "Ludwig".

Extracting the Inferred Type:
`type User = z.infer<typeof User>;`
After defining the schema, you use the z.infer utility to extract the TypeScript type inferred from the Zod schema. This results in a TypeScript type User that represents the expected structure of the object. In this case, the User type is inferred as { username: string }.
Using z.infer in Zod simplifies TypeScript integration by automatically deriving TypeScript types from your Zod schema. This single-source approach ensures consistency, reduces redundancy, and adapts types dynamically to changes in your validation rules.

**Primitives**

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

**Coercion for primitives**

Zod has introduced a convenient way to coerce primitive values with the coerce function. This allows for seamless conversion of various primitive types during the parsing step. Here's a breakdown and summary:

Coercion Example:
`const schema = z.coerce.string();
schema.parse("tuna"); // => "tuna"
schema.parse(12); // => "12"
schema.parse(true); // => "true"`
With z.coerce.string(), the input values are coerced into strings during the parsing process. The String() function, a JavaScript built-in for string coercion, is applied. The returned schema is a ZodString instance, enabling the use of all string methods.

Chained Coercion and Validation:
`z.coerce.string().email().min(5);`
Chaining is supported, allowing you to perform additional validations after coercion. In this example, the schema coerces to a string, validates the value as an email, and then checks for a minimum length of 5 characters.

Supported Coercions:
`z.coerce.string(); // String(input)
z.coerce.number(); // Number(input)
z.coerce.boolean(); // Boolean(input)
z.coerce.bigint(); // BigInt(input)
z.coerce.date(); // new Date(input)`

**Strings**

Zod includes a handful of string-specific validations.

`// validations
z.string().max(5);
z.string().min(5);
z.string().length(5);
z.string().email();
z.string().url();
z.string().emoji();
z.string().uuid();
z.string().cuid();
z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().datetime(); // ISO 8601; default is without UTC offset, see below for options
z.string().ip(); // defaults to IPv4 and IPv6, see below for options`

// transformations
`z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toUpperCase`

You can customize some common error messages when creating a string schema.

`const name = z.string({
  required_error: "Name is required",
  invalid_type_error: "Name must be a string",
});`
When using validation methods, you can pass in an additional argument to provide a custom error message.

`z.string().min(5, { message: "Must be 5 or more characters long" });
z.string().max(5, { message: "Must be 5 or fewer characters long" });
z.string().length(5, { message: "Must be exactly 5 characters long" });
z.string().email({ message: "Invalid email address" });
z.string().url({ message: "Invalid url" });
z.string().emoji({ message: "Contains non-emoji characters" });
z.string().uuid({ message: "Invalid UUID" });
z.string().includes("tuna", { message: "Must include tuna" });
z.string().startsWith("https://", { message: "Must provide secure URL" });
z.string().endsWith(".com", { message: "Only .com domains allowed" });
z.string().datetime({ message: "Invalid datetime string! Must be UTC." });
z.string().ip({ message: "Invalid IP address" });`

## Conclusion

In a nutshell, Zod empowers developers to focus on building robust applications by providing a clear and efficient solution to data validation challenges. Its elegant design, TypeScript integration, and continuous evolution make it a valuable tool for simplifying validation logic and improving overall code quality. Consider integrating Zod into your projects, and experience a paradigm shift in how you approach schema validation in your applications.