Quick Links:
- Website: https://zod.dev/
- Primitives: https://zod.dev/?id=primitives
- Used in my project:
import { z } from "zod";
import { deepEqual, equal } from 'assert'
// ❤️ Topics:
// - Safe (throw error on validation errors) and,
// - Unsafe parsing via `safeParse()` (without throwing errors on validation errors)
// - 3 Ways to give custom error message
// * primitive types
// z.string()
// z.number()
// z.bigint();
// z.boolean();
// z.symbol();
// z.undefined();
// z.null();
// * More
// z.array()
// z.string().datetime(),
// z.string().regex(SIMPLE_MONGODB_ID_REGEX),
// z.string().datetime().optional(), // Making anything optional
// E.g.,
// const CarStatusEnumSchema = z.enum(CarStatusEnum).default(CarStatusEnum.AVAILABLE).optional()
// Note: `optiona()` must occur after default(..) else optional doesn't work. [TESTED]
// * 1. Unsafe Parsing - Throw erron if validation fails.
equal(z.string().parse("tuna"), "tuna")
let e: any
try { z.string().parse(12) } catch (error) { e = error }
equal(e.issues[0].message, 'Invalid input: expected string, received number')
// * 2. Safe Parsing via `safeParse` - No error thrown when validation fails
deepEqual(z.string().safeParse("tuna"), { success: true, data: "tuna" })
// console.log(nameSchema.safeParse(12)); // { success: false; error: `ZodError instance` }
// & We use `safeParse` in below code for all of our below testing
// because I like to use it in production code as well.
// ❤️ Default error messages at `result.error.issues[*].message` property.
equal(z.string().safeParse('Sahil').data, 'Sahil') // No validation error
equal(z.string().safeParse(123).error?.name, 'ZodError')
equal(z.string().safeParse(123).error?.issues[0].message, 'Invalid input: expected string, received number')
equal(z.string().safeParse(123).error?.issues.map(i => i.message).join(', '), 'Invalid input: expected string, received number')
equal(z.string().safeParse(123).error?.message, `[
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]`)
// ❤️ CUSTOM ERROR MESSAGE (1) (Source: https://zod.dev/error-customization)
equal(z.string("Please provide string type only.").safeParse('Sahil').data, 'Sahil') // No validation error
equal(z.string("Please provide string type only.").safeParse(123).error?.name, 'ZodError')
equal(
z.string("Please provide string type only.").safeParse(123).error?.issues[0].message,
"Please provide string type only.",
)
equal(
z.string("Please provide string type only.").safeParse(123).error?.message,
`[
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Please provide string type only."
}
]`
)
// ❤️ CUSTOM ERROR MESSAGE (2) - Another way to pass custom error message is via passing an object:
equal(
z.string({ error: "Please provide string type only." }).safeParse(123).error?.name,
'ZodError',
)
equal(
z.string({ error: "Please provide string type only." }).safeParse(123).error?.message,
`[
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Please provide string type only."
}
]`,
)
equal(
z.string({ error: "Please provide string type only." }).safeParse(123).error?.issues[0].message,
'Please provide string type only.',
)
// ❤️ CUSTOM ERROR MESSAGE (3) - Fro docs: The error param optionally
// accepts a function. An error customization function is known as an
// error map in Zod terminology. The error map will run at parse time
// if a validation error occurs.
equal(z.string({
error: (issue) => {
const { code, input, expected } = issue
equal(issue.expected, 'string')
equal(issue.code, 'invalid_type')
equal(issue.input, 123)
return "Please provide string type only. ";
}
}).safeParse(123).error?.issues[0].message, "Please provide string type only. ")import { z } from "zod";
// ❤️ Topics:
// - Accessing nested schema
// User
console.log('\nUser 🚀')
const UserSchema = z.object({
username: z.string(),
phone: z.number(),
});
console.log(UserSchema.parse({ username: "Ludwig", phone: 1234 })); // { username: 'Ludwig', phone: 1234 }
const result2 = UserSchema.safeParse({ username: 42, phone: "100" });
if (!result2.success) {
// console.log("❌ ~ result2:", result2.error) // `ZodError instance`
console.log("❌ ~ result2.error.issues:", result2.error.issues)
/**
[
{
expected: 'string',
code: 'invalid_type',
path: [ 'username' ],
message: 'Invalid input: expected string, received number'
},
{
expected: 'number',
code: 'invalid_type',
path: [ 'phone' ],
message: 'Invalid input: expected number, received string'
}
]
*/
// console.log(z.prettifyError(result.error))
/**
✖ Invalid input: expected string, received number
→ at username
✖ Invalid input: expected number, received string
→ at phone
*/
} else {
result2.data;
}
// ✅ Extract the inferred type
type UserSchema = z.infer<typeof UserSchema>;
// Create new schema with all fields optional
const partialUserSchema = UserSchema.partial();
// ❤️ Accessing a nested schema
const CarSchema = z.object({ name: z.string(), price: z.number() });
console.log(CarSchema.shape.name.parse("BMW")) // "BMW"
// TODO ❤️ ❤️ ❤️ : https://www.youtube.com/watch?v=9UVPk0Ulm6Uimport { z } from "zod";
// ❤️ Topics:
// - Practical mongodb model schema usage.
const SIMPLE_MONGODB_ID_REGEX = /^[a-f\d]{24}$/i;
// From Piyush's Project
// Job
const JobSchema = z.object({
order: z.string().regex(SIMPLE_MONGODB_ID_REGEX),
images: z.array(z.object({
public_id: z.string(),
url: z.string(),
})),
itemName: z.string(),
costPerUnit: z.number(),
inventory: z.null(),
quantity: z.number(),
});
JobSchema.parse({
order: '5c9cb7138a874f1dcd0d8dcc',
images: [{
public_id: 'abc',
url: 'abc',
}],
itemName: 'abc',
costPerUnit: 0,
inventory: null,
quantity: 0,
});
type JobType = z.infer<typeof JobSchema>;
// JobTask
const JobTaskSchema = z.object({
order: z.string().regex(SIMPLE_MONGODB_ID_REGEX),
job: z.string().regex(SIMPLE_MONGODB_ID_REGEX),
jobTaskType: z.number(),
status: z.number(),
type: z.string(),
notes: z.string(),
user: z.string().regex(SIMPLE_MONGODB_ID_REGEX),
// `deadline` is optional here because we may want to set this on backend instead of getting from frontend.
deadline: z.string().datetime().optional(),
});
JobTaskSchema.parse({
order: '5c9cb7138a874f1dcd0d8dcc',
job: '5c9cb7138a874f1dcd0d8dcc',
jobTaskType: 0,
status: 0,
type: 'abc',
notes: 'abc',
user: '5c9cb7138a874f1dcd0d8dcc',
deadline: '2023-11-15T13:40:18.365Z',
});
type JobTaskType = z.infer<typeof JobTaskSchema>;
// Order
const OrderSchema = z.object({
user: z.string().regex(SIMPLE_MONGODB_ID_REGEX),
paymentInfo: z.object({
subTotal: z.number(),
discount: z.number(),
tax: z.number(),
totalAmount: z.number(),
paidAmount: z.number(),
remainingAmount: z.number(),
}),
note: z.string(),
customer: z.string().regex(SIMPLE_MONGODB_ID_REGEX),
shippingInfo: z.object({
name: z.string(),
phoneNumber: z.number(),
deadline: z.string().datetime(),
address: z.string(),
instructions: z.string(),
})
});
OrderSchema.parse({
user: '5c9cb7138a874f1dcd0d8dcc',
paymentInfo: {
subTotal: 0,
discount: 0,
tax: 0,
totalAmount: 0,
paidAmount: 0,
remainingAmount: 0,
},
note: 'abc',
customer: '5c9cb7138a874f1dcd0d8dcc',
shippingInfo: {
name: 'abc',
phoneNumber: 0,
deadline: '2023-11-15T13:40:18.365Z',
address: 'abc',
instructions: 'abc',
}
});
type OrderType = z.infer<typeof OrderSchema>;