Skip to content

Usage Examples

This page provides practical examples of using Zod validation in real-world scenarios.

API Request Validation

Express.js Middleware

Create reusable validation middleware for Express.js:

typescript
import { Request, Response, NextFunction } from 'express';
import { validateUserInput, validateServerConfig } from './validation.js';

// Generic validation middleware
function validateBody<T>(validator: (data: unknown) => { success: boolean; data?: T; error?: string }) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = validator(req.body);
    
    if (!result.success) {
      return res.status(400).json({
        error: 'Validation failed',
        details: result.error
      });
    }
    
    // Attach validated data to request
    req.validatedBody = result.data;
    next();
  };
}

// Use in routes
app.post('/api/users', 
  validateBody(validateUserInput),
  (req, res) => {
    const user = req.validatedBody; // Fully typed!
    // Create user logic...
    res.json({ success: true, user });
  }
);

Fastify Integration

typescript
import Fastify from 'fastify';
import { UserInputSchema } from './validation.js';

const fastify = Fastify();

// Register schema for automatic validation
fastify.post('/users', {
  schema: {
    body: UserInputSchema
  }
}, async (request, reply) => {
  // request.body is automatically validated and typed
  const user = request.body;
  return { success: true, user };
});

Configuration Loading

Application Startup

typescript
import { ConfigManager } from './config.js';
import { createLoggerFromConfig } from './logger.js';

async function startApplication() {
  // Load and validate configuration
  const configManager = new ConfigManager();
  const configResult = configManager.loadConfig();
  
  if (!configResult.success) {
    console.error('Failed to load configuration:', configResult.error);
    process.exit(1);
  }
  
  const config = configResult.data;
  
  // Create logger from validated config
  const logger = createLoggerFromConfig(config.logging);
  logger.info(`Starting ${config.app.name} v${config.app.version}`);
  
  // Start server with validated config
  const server = createServer(config.server);
  server.listen(config.server.port, config.server.host, () => {
    logger.info(`Server running on ${config.server.host}:${config.server.port}`);
  });
}

startApplication().catch(console.error);

Environment-Specific Configuration

typescript
import { validateAppConfig } from './validation.js';

function loadEnvironmentConfig() {
  const env = process.env.NODE_ENV || 'development';
  const configPath = `config.${env}.json`;
  
  try {
    const configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
    const result = validateAppConfig(configData);
    
    if (!result.success) {
      throw new Error(`Invalid ${env} configuration: ${result.error}`);
    }
    
    return result.data;
  } catch (error) {
    console.error(`Failed to load ${env} configuration:`, error.message);
    throw error;
  }
}

Form Validation

React Hook Form Integration

typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { UserInputSchema } from './validation.js';

function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm({
    resolver: zodResolver(UserInputSchema)
  });

  const onSubmit = (data) => {
    // data is automatically validated and typed
    console.log('Valid user data:', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="Name" />
      {errors.name && <span>{errors.name.message}</span>}
      
      <input {...register('email')} placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}
      
      <input {...register('age', { valueAsNumber: true })} placeholder="Age" />
      {errors.age && <span>{errors.age.message}</span>}
      
      <button type="submit">Submit</button>
    </form>
  );
}

Vue.js Composition API

typescript
import { ref, computed } from 'vue';
import { validateUserInput } from './validation.js';

export function useUserValidation() {
  const formData = ref({
    name: '',
    email: '',
    age: undefined
  });
  
  const validationResult = computed(() => {
    return validateUserInput(formData.value);
  });
  
  const isValid = computed(() => validationResult.value.success);
  const errors = computed(() => 
    validationResult.value.success ? null : validationResult.value.error
  );
  
  const submitForm = () => {
    if (isValid.value) {
      const validatedData = validationResult.value.data;
      // Submit logic...
    }
  };
  
  return {
    formData,
    isValid,
    errors,
    submitForm
  };
}

Database Operations

Prisma Integration

typescript
import { PrismaClient } from '@prisma/client';
import { validateUserInput } from './validation.js';

const prisma = new PrismaClient();

async function createUser(userData: unknown) {
  // Validate input before database operation
  const result = validateUserInput(userData);
  
  if (!result.success) {
    throw new Error(`Invalid user data: ${result.error}`);
  }
  
  const validatedUser = result.data;
  
  // Create user with validated data
  const user = await prisma.user.create({
    data: {
      name: validatedUser.name,
      email: validatedUser.email,
      age: validatedUser.age,
      preferences: validatedUser.preferences
    }
  });
  
  return user;
}

MongoDB Integration

typescript
import { MongoClient } from 'mongodb';
import { validateUserInput } from './validation.js';

async function insertUser(db: Db, userData: unknown) {
  const result = validateUserInput(userData);
  
  if (!result.success) {
    throw new Error(`Validation failed: ${result.error}`);
  }
  
  const collection = db.collection('users');
  const insertResult = await collection.insertOne({
    ...result.data,
    createdAt: new Date(),
    updatedAt: new Date()
  });
  
  return insertResult;
}

File Processing

JSON File Validation

typescript
import { safeParseAndValidate, AppConfigSchema } from './validation.js';

async function processConfigFile(filePath: string) {
  try {
    const fileContent = await fs.readFile(filePath, 'utf-8');
    const result = safeParseAndValidate(fileContent, AppConfigSchema);
    
    if (!result.success) {
      console.error(`Invalid config file ${filePath}:`, result.error);
      return null;
    }
    
    console.log(`Loaded valid config from ${filePath}`);
    return result.data;
  } catch (error) {
    console.error(`Failed to read config file ${filePath}:`, error.message);
    return null;
  }
}

CSV Data Validation

typescript
import csv from 'csv-parser';
import { validateUserInput } from './validation.js';

function processUserCSV(filePath: string) {
  const validUsers = [];
  const errors = [];
  
  return new Promise((resolve, reject) => {
    fs.createReadStream(filePath)
      .pipe(csv())
      .on('data', (row) => {
        const result = validateUserInput(row);
        
        if (result.success) {
          validUsers.push(result.data);
        } else {
          errors.push({
            row,
            error: result.error
          });
        }
      })
      .on('end', () => {
        resolve({
          validUsers,
          errors,
          total: validUsers.length + errors.length
        });
      })
      .on('error', reject);
  });
}

Testing Validation

Unit Tests

typescript
import { describe, it, expect } from '@jest/globals';
import { validateUserInput } from './validation.js';

describe('User Input Validation', () => {
  it('should validate correct user input', () => {
    const validUser = {
      name: 'John Doe',
      email: 'john@example.com',
      age: 30
    };
    
    const result = validateUserInput(validUser);
    
    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.name).toBe('John Doe');
      expect(result.data.email).toBe('john@example.com');
      expect(result.data.age).toBe(30);
    }
  });
  
  it('should reject invalid email', () => {
    const invalidUser = {
      name: 'John Doe',
      email: 'invalid-email',
      age: 30
    };
    
    const result = validateUserInput(invalidUser);
    
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.error).toContain('Invalid email format');
    }
  });
});

Integration Tests

typescript
import request from 'supertest';
import { app } from './app.js';

describe('User API', () => {
  it('should create user with valid data', async () => {
    const validUser = {
      name: 'Jane Doe',
      email: 'jane@example.com',
      age: 25
    };
    
    const response = await request(app)
      .post('/api/users')
      .send(validUser)
      .expect(200);
    
    expect(response.body.success).toBe(true);
    expect(response.body.user.name).toBe('Jane Doe');
  });
  
  it('should reject invalid user data', async () => {
    const invalidUser = {
      name: '',
      email: 'invalid-email'
    };
    
    const response = await request(app)
      .post('/api/users')
      .send(invalidUser)
      .expect(400);
    
    expect(response.body.error).toBe('Validation failed');
    expect(response.body.details).toContain('Name is required');
  });
});

Performance Considerations

Schema Caching

typescript
// Cache compiled schemas for better performance
const schemaCache = new Map();

function getCachedValidator(schemaName: string, schema: ZodSchema) {
  if (!schemaCache.has(schemaName)) {
    schemaCache.set(schemaName, schema.parse.bind(schema));
  }
  return schemaCache.get(schemaName);
}

// Use cached validator
const validateUser = getCachedValidator('user', UserInputSchema);

Async Validation

typescript
// For large datasets, use async validation
async function validateLargeDataset(data: unknown[]) {
  const results = await Promise.allSettled(
    data.map(item => Promise.resolve(validateUserInput(item)))
  );
  
  const valid = [];
  const invalid = [];
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled' && result.value.success) {
      valid.push(result.value.data);
    } else {
      invalid.push({ index, data: data[index], error: result.reason });
    }
  });
  
  return { valid, invalid };
}

Next Steps

Released under the MIT License.