Skip to main content
Back to Elite Events

Elite Events Documentation

Technical documentation, guides, and API references for the Elite Events platform.

State Management/Validation Guide

Validation Guide

This guide covers the unified validation system for Elite Events.

Overview

All validation in the application uses a single, consolidated validation layer located at @/lib/validation. This provides:

  • Type-safe validation with Zod schemas
  • Consistent error formats across API and forms
  • Reusable schemas for common patterns
  • Middleware for API route validation

Quick Start

Importing

// Import schemas and utilities from single location
import {
  // Common schemas
  emailSchema,
  passwordSchema,
  paginationSchema,

  // Domain schemas
  loginSchema,
  signupSchema,
  createProductSchema,
  checkoutSchema,

  // Error utilities
  formatZodError,
  ValidationError,
} from '@/lib/validation';

// Import middleware separately (uses NextRequest, not available in Jest)
import { withValidation } from '@/lib/validation/middleware';

API Route Validation

Using withValidation Middleware

The recommended approach for API routes:

import { NextResponse } from 'next/server';
import { createProductSchema, paginationSchema } from '@/lib/validation';
import { withValidation } from '@/lib/validation/middleware';
import { z } from 'zod';

// POST with body validation
export const POST = withValidation(
  { body: createProductSchema },
  async (request, { body }) => {
    // body is fully typed as CreateProductInput
    const product = await createProduct(body);
    return NextResponse.json({ success: true, data: product });
  }
);

// GET with query validation
export const GET = withValidation(
  { query: paginationSchema },
  async (request, { query }) => {
    const { page, limit, sortBy, sortOrder } = query;
    const products = await getProducts({ page, limit, sortBy, sortOrder });
    return NextResponse.json({ success: true, data: products });
  }
);

// With route params
export const PUT = withValidation(
  {
    params: z.object({ id: z.coerce.number() }),
    body: updateProductSchema,
  },
  async (request, { params, body }) => {
    const product = await updateProduct(params.id, body);
    return NextResponse.json({ success: true, data: product });
  }
);

Manual Validation

For cases where middleware doesn't fit:

import { validateBody, validateQuery } from '@/lib/validation';

export async function POST(request: NextRequest) {
  const result = await validateBody(request, createProductSchema);
  if (!result.success) {
    return result.response; // Returns 400 with validation errors
  }

  const data = result.data; // Fully typed
  // ... handle request
}

Form Validation

Using useForm Hook

The useForm hook in @/lib/forms/useForm provides comprehensive form management:

'use client';

import { useForm } from '@/lib/forms/useForm';
import { signupSchema } from '@/lib/validation';

function SignupForm() {
  const {
    values,
    errors,
    touched,
    isValid,
    isSubmitting,
    setValue,
    setTouched,
    handleSubmit,
  } = useForm({
    schema: signupSchema,
    initialValues: {
      email: '',
      password: '',
      confirmPassword: '',
      firstName: '',
      lastName: '',
      acceptTerms: false,
    },
  });

  const onSubmit = handleSubmit(async (data) => {
    await signUp(data);
  });

  return (
    <form onSubmit={onSubmit}>
      <input
        value={values.email}
        onChange={(e) => setValue('email', e.target.value)}
        onBlur={() => setTouched('email', true)}
      />
      {touched.email && errors.email && (
        <span className="text-red-500">{errors.email}</span>
      )}
      {/* ... other fields */}
    </form>
  );
}

Available Schemas

Common Schemas

SchemaDescription
emailSchemaEmail with validation and normalization
passwordSchemaPassword with complexity requirements
simplePasswordSchemaPassword without complexity (for login)
nameSchemaNames (2-100 characters)
phoneSchemaPhone number validation
urlSchemaURL validation
slugSchemaURL-friendly slugs
idSchemaPositive integer IDs
uuidSchemaUUID strings
paginationSchemapage, limit, sortBy, sortOrder
addressSchemaStreet, city, state, zip, country

Auth Schemas

SchemaDescription
loginSchemaEmail + password + rememberMe
signupSchemaFull signup with confirmPassword
forgotPasswordSchemaEmail for password reset
resetPasswordSchemaToken + new password
changePasswordSchemaCurrent + new password

User Schemas

SchemaDescription
updateProfileSchemaProfile field updates
createAddressSchemaNew address creation
notificationPreferencesSchemaEmail/push/SMS prefs

Product Schemas

SchemaDescription
createProductSchemaFull product creation
updateProductSchemaPartial product update
productQuerySchemaProduct list filters
createReviewSchemaProduct review
updateInventorySchemaStock adjustments

Order Schemas

SchemaDescription
addToCartSchemaAdd item to cart
checkoutSchemaFull checkout data
updateOrderStatusSchemaOrder status changes
createPaymentSchemaPayment intent creation
applyPromoCodeSchemaPromo code application

Creating Custom Schemas

Extending Existing Schemas

import { z } from 'zod';
import { addressSchema, emailSchema } from '@/lib/validation';

// Extend with additional fields
const shippingAddressSchema = addressSchema.extend({
  name: z.string().min(2),
  phone: z.string().optional(),
  instructions: z.string().max(500).optional(),
});

// Make fields optional
const partialAddressSchema = addressSchema.partial();

// Pick specific fields
const cityStateSchema = addressSchema.pick({ city: true, state: true });

Composing Schemas

import { z } from 'zod';
import { emailSchema, nameSchema } from '@/lib/validation';

const contactFormSchema = z.object({
  name: nameSchema,
  email: emailSchema,
  subject: z.string().min(5).max(100),
  message: z.string().min(20).max(2000),
  category: z.enum(['general', 'support', 'sales', 'feedback']),
});

Error Handling

Error Types

interface ValidationError {
  field: string;   // e.g., "email" or "address.city"
  message: string; // Human-readable message
  code: string;    // Zod error code
}

Formatting Errors

import { formatZodError, formatZodErrorToFieldMap } from '@/lib/validation';

try {
  schema.parse(data);
} catch (error) {
  if (error instanceof ZodError) {
    // Array of { field, message, code }
    const errors = formatZodError(error);

    // Object of { [field]: message }
    const fieldErrors = formatZodErrorToFieldMap(error);
  }
}

Best Practices

  1. Import schemas from @/lib/validation - Single source of truth
  2. Import middleware from @/lib/validation/middleware - For API routes
  3. Use withValidation for API routes - Consistent error handling
  4. Use useForm for forms - Built-in debouncing and state management
  5. Extend existing schemas - Don't duplicate validation logic
  6. Use .optional() for optional fields - Be explicit about nullability
  7. Add custom error messages - Improve user experience

Migration from Old Systems

If you're updating code using older validation patterns:

// OLD - Don't use
import { validators } from '@/lib/core/validators';
import { withRequestValidation } from '@/lib/security/request-validator';

// NEW - Use this
import { emailSchema } from '@/lib/validation';
import { withValidation } from '@/lib/validation/middleware';

File Structure

src/lib/validation/
├── index.ts           # Main entry point (re-exports all)
├── schemas.ts         # Common primitive schemas
├── errors.ts          # Error types and utilities
├── middleware.ts      # withValidation HOC
├── api-validation.ts  # Legacy API validation (still supported)
├── schemas/
│   ├── index.ts       # Barrel export
│   ├── common.ts      # Re-exports from schemas.ts
│   ├── auth.ts        # Authentication schemas
│   ├── user.ts        # User/profile schemas
│   ├── product.ts     # Product/category schemas
│   └── order.ts       # Order/cart/checkout schemas
└── __tests__/         # Validation tests
Documentation | Elite Events | Philip Rehberger