🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Validação de Schema Zod
Exemplos de validação de schema Zod priorizada para TypeScript incluindo primitivas, objetos, arrays, transformações, validadores personalizados e tratamento de erros
💻 Conceitos Básicos de Zod e Primitivas typescript
🟢 simple
⭐⭐
Validação básica de schema Zod com tipos primitivos, validação simples e transformações
⏱️ 25 min
🏷️ zod, validation, typescript, runtime
Prerequisites:
TypeScript basics, JavaScript
// Zod Basics and Primitive Types Example
import { z } from 'zod';
// 1. Primitive Types
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
const undefinedSchema = z.undefined();
const nullSchema = z.null();
// Test primitive validation
console.log(stringSchema.parse('hello')); // ✅ 'hello'
console.log(numberSchema.parse(42)); // ✅ 42
console.log(booleanSchema.parse(true)); // ✅ true
console.log(dateSchema.parse(new Date())); // ✅ Date object
console.log(undefinedSchema.parse(undefined)); // ✅ undefined
console.log(nullSchema.parse(null)); // ✅ null
// 2. Type Constraints
const minStringLength = z.string().min(5);
const maxStringLength = z.string().max(10);
const emailRegex = z.string().email();
const urlRegex = z.string().url();
console.log(minStringLength.parse('hello world')); // ✅
console.log(maxStringLength.parse('short')); // ✅
console.log(emailRegex.parse('[email protected]')); // ✅
console.log(urlRegex.parse('https://example.com')); // ✅
// 3. Number Constraints
const positiveNumber = z.number().positive();
const nonNegativeNumber = z.number().nonnegative();
const maxNumber = z.number().max(100);
const minNumber = z.number().min(18);
const intNumber = z.number().int();
console.log(positiveNumber.parse(10)); // ✅
console.log(nonNegativeNumber.parse(0)); // ✅
console.log(maxNumber.parse(50)); // ✅
console.log(minNumber.parse(25)); // ✅
console.log(intNumber.parse(42)); // ✅
// 4. Optional and Nullable Types
const optionalString = z.string().optional();
const nullableString = z.string().nullable();
const optionalOrNullableString = z.string().optional().nullable();
console.log(optionalString.parse('hello')); // ✅ 'hello'
console.log(optionalString.parse(undefined)); // ✅ undefined
console.log(nullableString.parse(null)); // ✅ null
console.log(nullableString.parse('hello')); // ✅ 'hello'
// 5. Default Values
const stringWithDefault = z.string().default('default value');
const optionalStringWithDefault = z.string().optional().default('optional default');
console.log(stringWithDefault.parse(undefined)); // ✅ 'default value'
console.log(optionalStringWithDefault.parse(undefined)); // ✅ 'optional default'
// 6. Enum-like Values
const directionSchema = z.enum(['north', 'south', 'east', 'west']);
const brandSchema = z.enum(['Nike', 'Adidas', 'Puma', 'Reebok']);
console.log(directionSchema.parse('north')); // ✅ 'north'
console.log(brandSchema.parse('Nike')); // ✅ 'Nike'
// 7. Literal Types
const trueSchema = z.literal(true);
const numberLiteral = z.literal(42);
const stringLiteral = z.literal('specific-value');
console.log(trueSchema.parse(true)); // ✅ true
console.log(numberLiteral.parse(42)); // ✅ 42
console.log(stringLiteral.parse('specific-value')); // ✅ 'specific-value'
// 8. Union Types
const stringOrNumber = z.union([z.string(), z.number()]);
const colorSchema = z.union([z.literal('red'), z.literal('green'), z.literal('blue')]);
console.log(stringOrNumber.parse('hello')); // ✅ 'hello'
console.log(stringOrNumber.parse(42)); // ✅ 42
console.log(colorSchema.parse('red')); // ✅ 'red'
// 9. Intersection Types
const personName = z.object({
firstName: z.string(),
lastName: z.string(),
});
const personAge = z.object({
age: z.number().positive(),
});
const personWithDetails = z.intersection(personName, personAge);
console.log(personWithDetails.parse({
firstName: 'John',
lastName: 'Doe',
age: 30
})); // ✅
// 10. Safe Parsing
const result = stringSchema.safeParse(123);
if (result.success) {
console.log('Valid:', result.data);
} else {
console.log('Invalid:', result.error);
}
// 11. Custom Validation
const customEmailSchema = z.string().email().refine((email) => {
return email.endsWith('@company.com');
}, {
message: 'Email must be from @company.com domain',
path: ['domain']
});
try {
customEmailSchema.parse('[email protected]'); // ✅
customEmailSchema.parse('[email protected]'); // ❌
} catch (error) {
console.error('Custom validation error:', error);
}
// 12. Transformations
const stringToNumber = z.string().transform((str) => parseInt(str, 10));
const uppercaseString = z.string().transform((str) => str.toUpperCase());
const trimmedString = z.string().transform((str) => str.trim());
const transformedNumber = stringToNumber.parse('123'); // ✅ 123 (number)
const transformedUpper = uppercaseString.parse('hello'); // ✅ 'HELLO'
const transformedTrim = trimmedString.parse(' hello '); // ✅ 'hello'
// 13. Preprocessing and Postprocessing
const preprocessSchema = z.string().transform((str) => str.trim().toLowerCase());
const postprocessSchema = z.string().transform((str) => {
return {
original: str,
length: str.length,
uppercase: str.toUpperCase()
};
});
const preprocessResult = preprocessSchema.parse(' HELLO '); // ✅ 'hello'
const postprocessResult = postprocessSchema.parse('hello'); // ✅ { original: 'hello', length: 5, uppercase: 'HELLO' }
// 14. Pipeline Transformations
const dateToTimestamp = z.date().transform((date) => date.getTime());
const timestampToDate = z.number().transform((timestamp) => new Date(timestamp));
const timestamp = dateToTimestamp.parse(new Date()); // ✅ timestamp (number)
const date = timestampToDate.parse(Date.now()); // ✅ Date object
// 15. Error Customization
const customErrorSchema = z.string({
errorMap: (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_string) {
return { message: 'Custom error message for invalid string' };
}
if (issue.code === z.ZodIssueCode.too_small) {
return { message: 'String is too short!' };
}
return { message: ctx.defaultError };
},
}).min(5);
try {
customErrorSchema.parse('abc'); // ❌ Custom error
} catch (error) {
console.log('Custom error message');
}
// 16. Advanced Primitive Examples
// Password validation with multiple requirements
const passwordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.max(100, 'Password must be less than 100 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character');
// Username validation
const usernameSchema = z.string()
.min(3, 'Username must be at least 3 characters')
.max(20, 'Username must be less than 20 characters')
.regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores')
.refine((username) => !username.startsWith('_'), {
message: 'Username cannot start with an underscore'
})
.refine((username) => !username.endsWith('_'), {
message: 'Username cannot end with an underscore'
});
// Phone number validation
const phoneNumberSchema = z.string()
.regex(/^+?[1-9]d{1,14}$/, 'Invalid phone number format')
.transform((phone) => {
// Normalize phone number format
return phone.replace(/[^0-9+]/g, '');
});
// URL validation with protocols
const urlSchema = z.string().url().refine((url) => {
const protocols = ['http:', 'https:', 'ftp:'];
try {
const parsedUrl = new URL(url);
return protocols.includes(parsedUrl.protocol);
} catch {
return false;
}
}, {
message: 'URL must use http, https, or ftp protocol'
});
// Color validation
const colorHexSchema = z.string()
.regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, 'Invalid hex color format')
.transform((hex) => hex.toLowerCase());
const colorRgbSchema = z.string()
.regex(/^rgb(s*d+s*,s*d+s*,s*d+s*)$/i, 'Invalid RGB format')
.transform((rgb) => {
const matches = rgb.match(/d+/g);
if (!matches || matches.length !== 3) return null;
return {
r: parseInt(matches[0]),
g: parseInt(matches[1]),
b: parseInt(matches[2])
};
});
// Test advanced validation
try {
passwordSchema.parse('MySecureP@ssw0rd!'); // ✅
usernameSchema.parse('john_doe123'); // ✅
phoneNumberSchema.parse('+1-555-123-4567'); // ✅
urlSchema.parse('https://example.com'); // ✅
colorHexSchema.parse('#FF5733'); // ✅
colorRgbSchema.parse('rgb(255, 87, 51)'); // ✅
} catch (error) {
console.error('Advanced validation error:', error);
}
// 17. Usage Examples
// Form validation function
function validateUserInput(userData: unknown) {
const userSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().min(18).max(120),
website: z.string().url().optional(),
bio: z.string().max(500).optional()
});
const result = userSchema.safeParse(userData);
if (result.success) {
return { success: true, data: result.data };
} else {
return {
success: false,
errors: result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message
}))
};
}
}
// API response validation
function validateApiResponse(response: unknown) {
const responseSchema = z.object({
success: z.boolean(),
data: z.unknown().optional(),
error: z.string().optional(),
timestamp: z.string()
});
return responseSchema.parse(response);
}
// Environment variable validation
function validateEnvVars(envVars: Record<string, unknown>) {
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().transform(Number).pipe(z.number().positive()),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
API_KEY: z.string().min(16),
ENABLE_LOGGING: z.string().transform(Boolean).pipe(z.boolean()).default('true'),
MAX_CONNECTIONS: z.string().transform(Number).pipe(z.number().positive()).default('10')
});
return envSchema.parse(envVars);
}
// Example usage
console.log('\n--- Usage Examples ---');
const userInput = {
name: 'John Doe',
email: '[email protected]',
age: 25,
website: 'https://johndoe.com',
bio: 'Software developer'
};
const userValidation = validateUserInput(userInput);
console.log('User validation:', userValidation);
const apiResponse = {
success: true,
data: { message: 'Hello World' },
timestamp: new Date().toISOString()
};
const validatedResponse = validateApiResponse(apiResponse);
console.log('API response validation:', validatedResponse);
const envVars = {
NODE_ENV: 'development',
PORT: '3000',
DATABASE_URL: 'https://localhost:5432/mydb',
JWT_SECRET: 'my-super-secret-jwt-key-32-chars-long',
API_KEY: 'my-api-key-16-chars'
};
const validatedEnv = validateEnvVars(envVars);
console.log('Environment validation:', validatedEnv);
export {
stringSchema,
numberSchema,
booleanSchema,
dateSchema,
userSchema: z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().min(18).max(120),
website: z.string().url().optional(),
bio: z.string().max(500).optional()
}),
passwordSchema,
usernameSchema,
validateUserInput,
validateApiResponse,
validateEnvVars
};
💻 Objetos e Arrays Zod typescript
🟡 intermediate
⭐⭐⭐
Schemas Zod complexas para objetos, arrays, estruturas aninhadas e padrões de validação avançados
⏱️ 40 min
🏷️ zod, validation, typescript, schemas, objects
Prerequisites:
Zod basics, TypeScript, Advanced data structures
// Zod Objects and Arrays Example
import { z } from 'zod';
// 1. Basic Object Schema
const userSchema = z.object({
id: z.string().uuid(),
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().min(13).max(120),
isActive: z.boolean().default(true),
createdAt: z.date().default(new Date()),
});
type User = z.infer<typeof userSchema>;
const validUser = {
id: '550e8400-e29b-41d4-a716-446655440000',
name: 'John Doe',
email: '[email protected]',
age: 30,
isActive: true,
createdAt: new Date()
};
console.log(userSchema.parse(validUser)); // ✅
// 2. Partial Object (for updates)
const updateUserSchema = userSchema.partial();
const updateData = {
name: 'Jane Doe',
age: 28
};
console.log(updateUserSchema.parse(updateData)); // ✅
// 3. Required Object (no optional/extra properties)
const strictUserSchema = z.object({
name: z.string(),
email: z.string().email()
}).strict();
console.log(strictUserSchema.parse({ name: 'John', email: '[email protected]' })); // ✅
// strictUserSchema.parse({ name: 'John', email: '[email protected]', extra: 'value' }); // ❌
// 4. Object with Passthrough (allow extra properties)
const passthroughUserSchema = z.object({
name: z.string(),
email: z.string().email()
}).passthrough();
console.log(passthroughUserSchema.parse({
name: 'John',
email: '[email protected]',
role: 'admin',
department: 'IT'
})); // ✅, preserves extra properties
// 5. Object with Catchall (custom handling for extra properties)
const catchallUserSchema = z.object({
name: z.string(),
email: z.string().email()
}).catchall(z.string());
console.log(catchallUserSchema.parse({
name: 'John',
email: '[email protected]',
role: 'admin', // ✅, string value
score: 100 // ❌, not a string
}));
// 6. Array Schemas
const stringArraySchema = z.array(z.string());
const numberArraySchema = z.array(z.number());
const userArraySchema = z.array(userSchema);
console.log(stringArraySchema.parse(['hello', 'world'])); // ✅
console.log(numberArraySchema.parse([1, 2, 3])); // ✅
console.log(userArraySchema.parse([validUser, validUser])); // ✅
// 7. Array Constraints
const minLengthArray = z.array(z.string()).min(2);
const maxLengthArray = z.array(z.number()).max(5);
const exactLengthArray = z.array(z.string()).length(3);
console.log(minLengthArray.parse(['a', 'b', 'c'])); // ✅
console.log(maxLengthArray.parse([1, 2, 3])); // ✅
console.log(exactLengthArray.parse(['x', 'y', 'z'])); // ✅
// 8. Non-empty Array
const nonEmptyArray = z.array(z.string()).nonempty();
console.log(nonEmptyArray.parse(['hello'])); // ✅
// nonEmptyArray.parse([]); // ❌
// 9. Set-like Array (unique values)
const uniqueArray = z.array(z.string()).refine(
(arr) => new Set(arr).size === arr.length,
{ message: 'Array must contain unique values' }
);
console.log(uniqueArray.parse(['a', 'b', 'c'])); // ✅
// uniqueArray.parse(['a', 'b', 'a']); // ❌
// 10. Nested Objects
const addressSchema = z.object({
street: z.string(),
city: z.string(),
state: z.string().length(2),
zipCode: z.string().regex(/^d{5}(-d{4})?$/),
country: z.string().default('USA'),
coordinates: z.object({
lat: z.number().min(-90).max(90),
lng: z.number().min(-180).max(180)
}).optional()
});
const userWithAddressSchema = z.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
address: addressSchema,
phoneNumbers: z.array(z.string().regex(/^+?[1-9]d{1,14}$/)).optional(),
metadata: z.record(z.unknown()).optional()
});
const userWithAddress = {
id: '550e8400-e29b-41d4-a716-446655440000',
name: 'John Doe',
email: '[email protected]',
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zipCode: '10001',
coordinates: { lat: 40.7128, lng: -74.0060 }
},
phoneNumbers: ['+1-555-123-4567'],
metadata: {
lastLogin: new Date().toISOString(),
preferences: { theme: 'dark' }
}
};
console.log(userWithAddressSchema.parse(userWithAddress)); // ✅
// 11. Array of Objects
const postSchema = z.object({
id: z.string().uuid(),
title: z.string().min(1).max(200),
content: z.string().max(10000),
authorId: z.string().uuid(),
tags: z.array(z.string().min(1).max(20)).max(5),
published: z.boolean().default(false),
createdAt: z.date(),
updatedAt: z.date().optional()
});
const postsSchema = z.array(postSchema);
const posts = [
{
id: '550e8400-e29b-41d4-a716-446655440001',
title: 'Hello World',
content: 'This is my first post',
authorId: '550e8400-e29b-41d4-a716-446655440000',
tags: ['intro', 'hello'],
published: true,
createdAt: new Date()
},
{
id: '550e8400-e29b-41d4-a716-446655440002',
title: 'Second Post',
content: 'Another interesting post',
authorId: '550e8400-e29b-41d4-a716-446655440000',
tags: ['updates', 'announcement'],
published: false,
createdAt: new Date()
}
];
console.log(postsSchema.parse(posts)); // ✅
// 12. Discriminated Unions
const baseEventSchema = z.object({
id: z.string().uuid(),
timestamp: z.date(),
userId: z.string().uuid()
});
const userLoginEventSchema = baseEventSchema.extend({
type: z.literal('user.login'),
ipAddress: z.string().ip(),
userAgent: z.string()
});
const userLogoutEventSchema = baseEventSchema.extend({
type: z.literal('user.logout'),
sessionDuration: z.number().positive()
});
const postCreatedEventSchema = baseEventSchema.extend({
type: z.literal('post.created'),
postId: z.string().uuid(),
postTitle: z.string()
});
const eventSchema = z.discriminatedUnion('type', [
userLoginEventSchema,
userLogoutEventSchema,
postCreatedEventSchema
]);
type Event = z.infer<typeof eventSchema>;
// Valid events
const loginEvent = {
id: '550e8400-e29b-41d4-a716-446655440003',
timestamp: new Date(),
userId: '550e8400-e29b-41d4-a716-446655440000',
type: 'user.login' as const,
ipAddress: '192.168.1.1',
userAgent: 'Mozilla/5.0...'
};
const logoutEvent = {
id: '550e8400-e29b-41d4-a716-446655440004',
timestamp: new Date(),
userId: '550e8400-e29b-41d4-a716-446655440000',
type: 'user.logout' as const,
sessionDuration: 3600
};
const postEvent = {
id: '550e8400-e29b-41d4-a716-446655440005',
timestamp: new Date(),
userId: '550e8400-e29b-41d4-a716-446655440000',
type: 'post.created' as const,
postId: '550e8400-e29b-41d4-a716-446655440001',
postTitle: 'Hello World'
};
console.log(eventSchema.parse(loginEvent)); // ✅
console.log(eventSchema.parse(logoutEvent)); // ✅
console.log(eventSchema.parse(postEvent)); // ✅
// 13. Record Schema (key-value object)
const configSchema = z.record(z.string(), z.unknown());
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
features: {
darkMode: true,
notifications: false
}
};
console.log(configSchema.parse(config)); // ✅
// More specific record schema
const themeConfigSchema = z.record(z.enum(['primary', 'secondary', 'success', 'danger', 'warning', 'info']), z.string());
const themeConfig = {
header: 'primary',
sidebar: 'secondary',
buttons: 'success',
alerts: 'danger',
notifications: 'warning',
links: 'info'
};
console.log(themeConfigSchema.parse(themeConfig)); // ✅
// 14. Tuple Schema
const coordinateSchema = z.tuple([z.number(), z.number()]);
const rgbColorSchema = z.tuple([z.number().min(0).max(255), z.number().min(0).max(255), z.number().min(0).max(255)]);
console.log(coordinateSchema.parse([10, 20])); // ✅
console.log(rgbColorSchema.parse([255, 128, 0])); // ✅
// 15. Optional Array Elements
const profileSchema = z.object({
username: z.string(),
email: z.string().email(),
socialLinks: z.array(z.object({
platform: z.string(),
url: z.string().url()
})).optional(),
skills: z.array(z.string()).optional()
});
const profile = {
username: 'johndoe',
email: '[email protected]',
socialLinks: [
{ platform: 'twitter', url: 'https://twitter.com/johndoe' },
{ platform: 'github', url: 'https://github.com/johndoe' }
],
skills: ['TypeScript', 'React', 'Node.js']
};
console.log(profileSchema.parse(profile)); // ✅
// 16. Recursive Schema (self-referencing)
const categorySchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().optional(),
parentCategory: z.string().optional()
});
const categoryWithChildren = categorySchema.extend({
subcategories: z.lazy(() => z.array(categoryWithChildren)).optional()
});
// Valid category with nested children
const categoryTree = {
id: 'cat1',
name: 'Technology',
description: 'All things tech',
subcategories: [
{
id: 'cat2',
name: 'Programming',
subcategories: [
{
id: 'cat3',
name: 'JavaScript',
parentCategory: 'cat2'
},
{
id: 'cat4',
name: 'Python',
parentCategory: 'cat2'
}
]
},
{
id: 'cat5',
name: 'Hardware',
parentCategory: 'cat1'
}
]
};
console.log(categoryWithChildren.parse(categoryTree)); // ✅
// 17. Complex Validation Examples
// Product schema with inventory management
const productSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
description: z.string().max(1000),
price: z.number().positive(),
categories: z.array(z.string().uuid()).min(1),
variants: z.array(z.object({
id: z.string().uuid(),
name: z.string(),
sku: z.string(),
price: z.number().positive().optional(),
inventory: z.object({
quantity: z.number().min(0),
lowStockThreshold: z.number().min(0),
trackInventory: z.boolean()
}),
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
images: z.array(z.object({
url: z.string().url(),
alt: z.string().optional(),
isPrimary: z.boolean().default(false)
}))
})).min(1),
tags: z.array(z.string()).max(10).optional(),
metadata: z.object({
weight: z.number().positive().optional(),
dimensions: z.object({
length: z.number().positive(),
width: z.number().positive(),
height: z.number().positive(),
unit: z.enum(['cm', 'in', 'mm']).default('cm')
}).optional(),
seo: z.object({
title: z.string().max(60).optional(),
description: z.string().max(160).optional(),
keywords: z.array(z.string()).max(10).optional()
}).optional()
}).optional()
});
// Order schema with line items
const orderItemSchema = z.object({
productId: z.string().uuid(),
variantId: z.string().uuid(),
quantity: z.number().positive().max(100),
price: z.number().positive(),
discounts: z.array(z.object({
type: z.enum(['percentage', 'fixed']),
value: z.number().positive(),
reason: z.string().optional()
})).optional()
});
const orderSchema = z.object({
id: z.string().uuid(),
customerId: z.string().uuid(),
items: z.array(orderItemSchema).min(1),
status: z.enum(['pending', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled']),
shippingAddress: addressSchema,
billingAddress: addressSchema,
paymentMethod: z.object({
type: z.enum(['credit_card', 'debit_card', 'paypal', 'stripe']),
lastFour: z.string().length(4).regex(/^d+$/),
brand: z.string().optional()
}),
subtotal: z.number().positive(),
tax: z.number().nonnegative(),
shipping: z.number().nonnegative(),
total: z.number().positive(),
createdAt: z.date(),
shippedAt: z.date().optional(),
deliveredAt: z.date().optional()
});
// Validation functions for complex scenarios
function validateProductInventory(product: unknown, requestedQuantity: number) {
const parsedProduct = productSchema.parse(product);
for (const variant of parsedProduct.variants) {
if (variant.inventory.quantity < requestedQuantity) {
throw new Error(`Insufficient inventory for variant ${variant.name}. Available: ${variant.inventory.quantity}, Requested: ${requestedQuantity}`);
}
if (variant.inventory.trackInventory && variant.inventory.quantity <= variant.inventory.lowStockThreshold) {
console.warn(`Low stock warning for variant ${variant.name}. Current stock: ${variant.inventory.quantity}`);
}
}
return parsedProduct;
}
function validateOrderTotal(order: unknown) {
const parsedOrder = orderSchema.parse(order);
const calculatedSubtotal = parsedOrder.items.reduce((sum, item) => {
const itemTotal = item.price * item.quantity;
const discountTotal = (item.discounts || []).reduce((discountSum, discount) => {
if (discount.type === 'percentage') {
return discountSum + (itemTotal * discount.value / 100);
} else {
return discountSum + discount.value;
}
}, 0);
return sum + (itemTotal - discountTotal);
}, 0);
if (Math.abs(calculatedSubtotal - parsedOrder.subtotal) > 0.01) {
throw new Error(`Subtotal mismatch. Calculated: ${calculatedSubtotal}, Provided: ${parsedOrder.subtotal}`);
}
const calculatedTotal = calculatedSubtotal + parsedOrder.tax + parsedOrder.shipping;
if (Math.abs(calculatedTotal - parsedOrder.total) > 0.01) {
throw new Error(`Total mismatch. Calculated: ${calculatedTotal}, Provided: ${parsedOrder.total}`);
}
return parsedOrder;
}
// Test complex validation
try {
const testProduct = {
id: '550e8400-e29b-41d4-a716-446655440006',
name: 'Wireless Headphones',
description: 'High-quality wireless headphones with noise cancellation',
price: 99.99,
categories: ['cat1', 'cat2'],
variants: [
{
id: 'var1',
name: 'Black',
sku: 'WH-BLK-001',
inventory: {
quantity: 50,
lowStockThreshold: 10,
trackInventory: true
},
attributes: { color: 'black', wireless: true },
images: [
{ url: 'https://example.com/headphones-black.jpg', alt: 'Black wireless headphones', isPrimary: true }
]
}
]
};
validateProductInventory(testProduct, 5); // ✅
const testOrder = {
id: 'order1',
customerId: 'customer1',
items: [
{
productId: 'product1',
variantId: 'var1',
quantity: 2,
price: 99.99
}
],
status: 'pending' as const,
shippingAddress: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zipCode: '10001'
},
billingAddress: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zipCode: '10001'
},
paymentMethod: {
type: 'credit_card' as const,
lastFour: '1234',
brand: 'Visa'
},
subtotal: 199.98,
tax: 16.00,
shipping: 5.00,
total: 220.98,
createdAt: new Date()
};
validateOrderTotal(testOrder); // ✅
} catch (error) {
console.error('Complex validation error:', error);
}
export {
userSchema,
addressSchema,
userWithAddressSchema,
postSchema,
postsSchema,
eventSchema,
productSchema,
orderItemSchema,
orderSchema,
validateProductInventory,
validateOrderTotal,
type User,
type Event
};
💻 Padrões Avançados de Zod typescript
🔴 complex
⭐⭐⭐⭐⭐
Padrões avançados de Zod incluindo validadores personalizados, tratamento de erros, composição de schemas, middleware e casos de uso do mundo real
⏱️ 60 min
🏷️ zod, validation, patterns, typescript, advanced
Prerequisites:
Advanced Zod, TypeScript, Design patterns, Async/await
// Zod Advanced Patterns and Real-World Examples
import { z, ZodIssue, ZodSchema } from 'zod';
// 1. Custom Validation with Detailed Error Messages
// Enhanced string validation with multiple custom rules
const enhancedStringSchema = z.string()
.min(3, 'String must be at least 3 characters long')
.max(100, 'String must be at most 100 characters long')
.regex(/^[a-zA-Z0-9\s-_]+$/, 'String can only contain letters, numbers, spaces, hyphens, and underscores')
.refine((val) => val.trim() === val, {
message: 'String cannot have leading or trailing whitespace'
})
.refine((val) => {
const wordCount = val.trim().split(/\s+/).length;
return wordCount >= 2;
}, {
message: 'String must contain at least 2 words'
})
.transform((val) => val.trim());
// Custom ID validator with format checking
const idSchema = z.string()
.regex(/^[a-zA-Z0-9_-]+$/, 'ID can only contain letters, numbers, hyphens, and underscores')
.min(3, 'ID must be at least 3 characters long')
.max(50, 'ID must be at most 50 characters long')
.refine((val) => /^[a-zA-Z]/.test(val), {
message: 'ID must start with a letter'
})
.refine((val) => !/[-_]{2,}/.test(val), {
message: 'ID cannot contain consecutive hyphens or underscores'
});
// 2. Schema Composition and Reuse
// Base field schemas for reuse
const baseFieldSchemas = {
id: idSchema,
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
createdAt: z.date(),
updatedAt: z.date().optional(),
isActive: z.boolean().default(true)
};
// Timestamp schema with validation
const timestampSchema = z.date()
.refine((date) => date <= new Date(), {
message: 'Date cannot be in the future'
})
.refine((date) => date >= new Date('2000-01-01'), {
message: 'Date must be after year 2000'
});
// 3. Advanced Error Handling
// Custom error formatting
const formatZodError = (error: z.ZodError) => {
return {
success: false,
error: {
code: error.issues[0]?.code || 'VALIDATION_ERROR',
message: error.issues[0]?.message || 'Validation failed',
field: error.issues[0]?.path?.join('.') || 'unknown',
details: error.issues.map(issue => ({
field: issue.path?.join('.') || 'unknown',
code: issue.code,
message: issue.message,
received: issue.received,
expected: issue.expected
}))
}
};
};
// Custom error class for validation
class ValidationError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly field?: string,
public readonly details?: ZodIssue[]
) {
super(message);
this.name = 'ValidationError';
}
}
// Enhanced safe parse with custom error handling
function safeParseWithCustomError<T>(schema: ZodSchema<T>, data: unknown): T {
const result = schema.safeParse(data);
if (!result.success) {
const firstError = result.error.issues[0];
throw new ValidationError(
firstError.message,
firstError.code,
firstError.path?.join('.'),
result.error.issues
);
}
return result.data;
}
// 4. Middleware Pattern for Validation
// Validation middleware type
type ValidationMiddleware<T> = (data: unknown) => T;
// Create validation middleware
function createValidationMiddleware<T>(schema: ZodSchema<T>): ValidationMiddleware<T> {
return (data: unknown): T => {
return safeParseWithCustomError(schema, data);
};
}
// Higher-order function for validation
function withValidation<T>(
schema: ZodSchema<T>,
fn: (validatedData: T) => unknown
) {
const validate = createValidationMiddleware(schema);
return (data: unknown) => {
const validatedData = validate(data);
return fn(validatedData);
};
}
// 5. Async Validation Patterns
// Async validator for checking uniqueness
async function createUniqueEmailValidator(existingEmails: Set<string>) {
return async (email: string) => {
// Simulate database check
await new Promise(resolve => setTimeout(resolve, 100));
if (existingEmails.has(email.toLowerCase())) {
throw new Error('Email already exists');
}
return true;
};
}
// Async schema with custom validation
async function createUserSchema(existingEmails: Set<string> = new Set()) {
const uniqueEmailValidator = await createUniqueEmailValidator(existingEmails);
return z.object({
email: z.string().email().refine(uniqueEmailValidator, {
message: 'Email must be unique'
}),
username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
password: z.string()
.min(8)
.regex(/[A-Z]/, 'Password must contain uppercase')
.regex(/[a-z]/, 'Password must contain lowercase')
.regex(/[0-9]/, 'Password must contain number')
.regex(/[^A-Za-z0-9]/, 'Password must contain special character')
});
}
// 6. Conditional Validation
// Conditional object schema based on user type
function createUserProfileSchema(isAdmin: boolean) {
const baseProfile = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
bio: z.string().max(500).optional()
});
if (isAdmin) {
return baseProfile.extend({
adminLevel: z.enum(['junior', 'senior', 'lead']),
permissions: z.array(z.string()).min(1),
department: z.string().min(1)
});
}
return baseProfile.extend({
preferences: z.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean(),
language: z.string().length(2)
})
});
}
// 7. Schema Refactoring with Composition
// Address schema
const addressSchema = z.object({
street: z.string().min(1),
city: z.string().min(1),
state: z.string().length(2),
zipCode: z.string().regex(/^\d{5}(-\d{4})?$/),
country: z.string().default('USA'),
addressType: z.enum(['shipping', 'billing', 'both'])
});
// Contact schema
const contactSchema = z.object({
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
email: z.string().email(),
website: z.string().url().optional(),
socialMedia: z.record(z.string().url()).optional()
});
// User schema composed from smaller schemas
const createUserSchema = () => z.object({
...baseFieldSchemas,
contact: contactSchema,
addresses: z.array(addressSchema).min(1),
role: z.enum(['user', 'admin', 'moderator']).default('user'),
profile: z.object({
avatar: z.string().url().optional(),
timezone: z.string(),
locale: z.string().default('en-US')
})
});
// 8. Real-World API Request/Response Validation
// API Request schemas
const PaginationSchema = z.object({
page: z.coerce.number().positive().default(1),
limit: z.coerce.number().positive().max(100).default(20),
sortBy: z.enum(['createdAt', 'updatedAt', 'name']).default('createdAt'),
sortOrder: z.enum(['asc', 'desc']).default('desc')
});
const FilterSchema = z.object({
status: z.enum(['active', 'inactive', 'all']).optional(),
category: z.string().optional(),
dateFrom: z.string().datetime().optional(),
dateTo: z.string().datetime().optional(),
search: z.string().optional()
});
// API Request wrapper
const ApiRequestSchema = z.object({
pagination: PaginationSchema,
filters: FilterSchema,
include: z.array(z.string()).optional()
});
// API Response schemas
const ApiSuccessSchema = <T>(dataSchema: ZodSchema<T>) => z.object({
success: z.literal(true),
data: dataSchema,
pagination: z.object({
currentPage: z.number(),
totalPages: z.number(),
totalItems: z.number(),
hasNext: z.boolean(),
hasPrev: z.boolean()
}).optional(),
meta: z.record(z.unknown()).optional()
});
const ApiErrorSchema = z.object({
success: z.literal(false),
error: z.object({
code: z.string(),
message: z.string(),
details: z.array(z.object({
field: z.string(),
code: z.string(),
message: z.string()
})).optional()
}),
meta: z.record(z.unknown()).optional()
});
// Generic API response schema
const ApiResponseSchema = <T>(dataSchema: ZodSchema<T>) =>
z.union([ApiSuccessSchema(dataSchema), ApiErrorSchema]);
// 9. Database Model Validation
// UUID validator with fallback
const uuidSchema = z.string().uuid().or(z.string().length(36)).transform((val) => {
// Handle both UUID and string representation
return val.length === 36 ? val : val;
});
// Entity status validator
const EntityStatusSchema = z.enum(['active', 'inactive', 'archived', 'pending']).default('active');
// Soft delete mixin
const withSoftDelete = <T extends z.ZodRawShape>(
schema: T
) => z.object(schema).extend({
deletedAt: z.date().nullable().default(null),
deletedBy: uuidSchema.nullable().default(null)
});
// Audit fields mixin
const withAuditFields = <T extends z.ZodRawShape>(
schema: T
) => z.object(schema).extend({
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
createdBy: uuidSchema,
updatedBy: uuidSchema.optional()
});
// 10. Environment Configuration Validation
// Environment type discriminator
const EnvironmentSchema = z.enum(['development', 'staging', 'production', 'test']);
// Database configuration
const DatabaseConfigSchema = z.object({
url: z.string().url(),
ssl: z.boolean().default(true),
maxConnections: z.number().positive().default(10),
connectionTimeout: z.number().positive().default(30000),
idleTimeout: z.number().positive().default(300000)
});
// Server configuration
const ServerConfigSchema = z.object({
port: z.number().positive().default(3000),
host: z.string().default('localhost'),
cors: z.object({
origin: z.union([z.string(), z.array(z.string())]).default('*'),
credentials: z.boolean().default(false),
methods: z.array(z.enum(['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])).default(['GET', 'POST'])
}).default({}),
rateLimit: z.object({
windowMs: z.number().positive().default(900000), // 15 minutes
maxRequests: z.number().positive().default(100)
}).optional()
});
// Complete environment configuration
const EnvironmentConfigSchema = z.object({
nodeEnv: EnvironmentSchema,
server: ServerConfigSchema,
database: DatabaseConfigSchema,
auth: z.object({
jwtSecret: z.string().min(32),
jwtExpiresIn: z.string().default('24h'),
bcryptRounds: z.number().positive().min(10).max(12).default(12)
}),
redis: z.object({
url: z.string().url().optional(),
host: z.string().default('localhost'),
port: z.number().positive().default(6379),
password: z.string().optional(),
db: z.number().default(0)
}).optional(),
features: z.object({
enableMetrics: z.boolean().default(false),
enableLogging: z.boolean().default(true),
enableCache: z.boolean().default(true),
enableRateLimit: z.boolean().default(true)
}).default({})
});
// 11. Form Validation Patterns
// Multi-step form schema
const step1Schema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/)
});
const step2Schema = z.object({
address: addressSchema,
preferences: z.object({
newsletter: z.boolean().default(false),
marketing: z.boolean().default(false)
})
});
const step3Schema = z.object({
paymentMethod: z.enum(['credit_card', 'paypal', 'bank_transfer']),
terms: z.boolean().refine(val => val === true, {
message: 'You must accept the terms and conditions'
})
});
// Multi-step form validator
function validateMultiStepForm(step: number, data: unknown) {
switch (step) {
case 1:
return step1Schema.parse(data);
case 2:
return step2Schema.parse(data);
case 3:
return step3Schema.parse(data);
default:
throw new Error('Invalid step number');
}
}
// 12. Real-World Usage Examples
// User registration service
class UserService {
private existingEmails = new Set<string>();
private existingUsernames = new Set<string>();
async registerUser(userData: unknown) {
const userSchema = await this.createUserRegistrationSchema();
const validatedUser = safeParseWithCustomError(userSchema, userData);
// Additional business logic
await this.checkDuplicateEmail(validatedUser.email);
await this.checkDuplicateUsername(validatedUser.username);
const user = await this.createUserInDatabase(validatedUser);
// Update tracking sets
this.existingEmails.add(user.email.toLowerCase());
this.existingUsernames.add(user.username.toLowerCase());
return user;
}
private async createUserRegistrationSchema() {
const uniqueEmailValidator = await this.createUniqueEmailValidator();
return z.object({
username: z.string()
.min(3)
.max(20)
.regex(/^[a-zA-Z0-9_]+$/)
.refine(async (username) => {
await new Promise(resolve => setTimeout(resolve, 50)); // Simulate async check
return !this.existingUsernames.has(username.toLowerCase());
}, {
message: 'Username already exists'
}),
email: z.string().email(),
password: z.string().min(8),
confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword']
});
}
private async createUniqueEmailValidator() {
return async (email: string) => {
await new Promise(resolve => setTimeout(resolve, 50));
return !this.existingEmails.has(email.toLowerCase());
};
}
private async checkDuplicateEmail(email: string) {
if (this.existingEmails.has(email.toLowerCase())) {
throw new ValidationError('Email already exists', 'DUPLICATE_EMAIL', 'email');
}
}
private async checkDuplicateUsername(username: string) {
if (this.existingUsernames.has(username.toLowerCase())) {
throw new ValidationError('Username already exists', 'DUPLICATE_USERNAME', 'username');
}
}
private async createUserInDatabase(userData: any) {
// Simulate database insertion
await new Promise(resolve => setTimeout(resolve, 100));
return {
id: Math.random().toString(36).substr(2, 9),
...userData,
createdAt: new Date(),
updatedAt: new Date()
};
}
}
// API request/response handler
class ApiController {
handleRequest(request: unknown, responseSchema: ZodSchema<any>) {
try {
const validatedRequest = safeParseWithCustomError(
ApiRequestSchema,
request
);
// Process request...
const data = await this.processRequest(validatedRequest);
const validatedResponse = ApiSuccessSchema(responseSchema).parse({
success: true,
data
});
return validatedResponse;
} catch (error) {
if (error instanceof ValidationError) {
return ApiErrorSchema.parse({
success: false,
error: {
code: error.code,
message: error.message,
details: error.details?.map(issue => ({
field: issue.field,
code: issue.code,
message: issue.message
}))
}
});
}
// Handle other unexpected errors
return ApiErrorSchema.parse({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred'
}
});
}
}
private async processRequest(request: any) {
// Simulate async processing
await new Promise(resolve => setTimeout(resolve, 50));
return { message: 'Request processed successfully' };
}
}
// 13. Testing with Zod
// Test utilities
function expectValidationSuccess<T>(schema: ZodSchema<T>, data: unknown): T {
const result = schema.safeParse(data);
if (!result.success) {
throw new Error(`Expected validation to succeed, but it failed: ${result.error.message}`);
}
return result.data;
}
function expectValidationFailure<T>(schema: ZodSchema<T>, data: unknown, expectedError?: string): void {
const result = schema.safeParse(data);
if (result.success) {
throw new Error('Expected validation to fail, but it succeeded');
}
if (expectedError && !result.error.message.includes(expectedError)) {
throw new Error(`Expected error message to contain "${expectedError}", but got: ${result.error.message}`);
}
}
// Test examples
function runTests() {
console.log('Running Zod validation tests...');
// Test success cases
const validUser = expectValidationSuccess(userSchema, {
id: '550e8400-e29b-41d4-a716-446655440000',
name: 'John Doe',
email: '[email protected]',
age: 30
});
console.log('✅ Valid user test passed');
// Test failure cases
expectValidationFailure(
userSchema,
{ id: 'invalid-uuid', name: '', email: 'invalid-email', age: -5 },
'Expected string'
);
console.log('✅ Invalid user test passed');
console.log('All tests completed successfully!');
}
// Execute tests
runTests();
// 14. Schema Documentation Generator
function generateSchemaDocumentation(schema: ZodSchema<any>, name: string): string {
const description = schema._def.description || name;
let docs = `# ${name}\n\n${description}\n\n## Schema Definition\n\n`;
if (schema._def.typeName === 'ZodObject') {
docs += '| Property | Type | Required | Description |\n';
docs += '|---------|------|----------|-------------|\n';
for (const [key, def] of Object.entries(schema._def.shape())) {
const type = def._def.typeName;
const required = !def._def.defaultValue;
const description = def._def.description || '';
docs += `| ${key} | ${type} | ${required ? 'Yes' : 'No'} | ${description} |\n`;
}
}
return docs;
}
export {
enhancedStringSchema,
idSchema,
formatZodError,
ValidationError,
safeParseWithCustomError,
createValidationMiddleware,
withValidation,
ApiRequestSchema,
ApiResponseSchema,
EnvironmentConfigSchema,
validateMultiStepForm,
UserService,
ApiController,
expectValidationSuccess,
expectValidationFailure,
generateSchemaDocumentation
};