umma.dev

Zod

What is Zod?

Schema validation, usually used with TypeScript.

Set Up

You must enable strict mode on tsconfig.json.

npm install zod

Zod is also compatible with yarn, bun and pnpm.

Code Examples

Component Set Up

import { z } from "zod";

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

// if you wanted the inferred type
type User = z.infer<typeof User>;

Strings

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().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toUpperCase

z.string().datetime(); // ISO 8601
const datetime = z.string().datetime();
datetime.parse("2020-01-01T00:00:00Z"); // pass
datetime.parse("2020-01-01T00:00:00.123Z"); // pass
datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision)
datetime.parse("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed)

z.string().ip(); // IPv4/IPv6
const ip = z.string().ip();
ip.parse("192.168.1.1"); // pass
ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // pass
ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // pass
ip.parse("256.1.1.1"); // fail
ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // fail

Numbers

// custom error messages
const age = z.number({
  required_error: "Age is required",
  invalid_type_error: "Age must be a number",
});

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
z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER

Dates

z.date().min(new Date("1900-01-01"), { message: "Too old" });
z.date().max(new Date(), { message: "Too young!" });

Arrays

// these two are the same
const stringArray = z.array(z.string());
const stringArray = z.string().array();

z.string().optional().array(); // (string | undefined)[]
z.string().array().optional(); // string[] | undefined

z.string().array().nonempty();
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

Objects

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

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

const Recipe = z.object({
  id: z.string(),
  name: z.string(),
  ingredients: z.array(z.string()),
});
const JustTheName = Recipe.pick({ name: true });
const NoIDRecipe = Recipe.omit({ id: true });

const user = z.object({
  email: z.string(),
  username: z.string(),
});
const partialUser = user.partial();

const user = z
  .object({
    email: z.string(),
    username: z.string(),
  })
  .partial();
const requiredUser = user.required();

Functions

const trimmedLength = z
  .function()
  .args(z.string()) // accepts an arbitrary number of arguments
  .returns(z.number())
  .implement((x) => {
    // TypeScript knows x is a string!
    return x.trim().length;
  });

trimmedLength("sandwich"); // => 8
trimmedLength(" asdf "); // => 4

Promises

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
};

Enums

enum Fruits {
  Apple,
  Banana,
}
const FruitEnum = z.nativeEnum(Fruits);
type FruitEnum = z.infer<typeof FruitEnum>; // Fruits
FruitEnum.parse(Fruits.Apple); // passes
FruitEnum.parse(Fruits.Banana); // passes
FruitEnum.parse(0); // passes
FruitEnum.parse(1); // passes
FruitEnum.parse(3); // fails

Tuples, Unions, Sets, Maps

Tuples

const athleteSchema = z.tuple([
  z.string(), // name
  z.number(), // jersey number
  z.object({
    pointsScored: z.number(),
  }), // statistics
]);

Unions

const stringOrNumber = z.union([z.string(), z.number()]);
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes

const stringOrNumber = z.string().or(z.number());

Sets

z.set(z.string()).nonempty(); // must contain at least one item
z.set(z.string()).min(5); // must contain 5 or more items
z.set(z.string()).max(5); // must contain 5 or fewer items
z.set(z.string()).size(5); // must contain 5 items exactly

Maps

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