typescriptintermediate

Request Validation Schema Builder

Build a lightweight request validation layer with type inference for API endpoints.

typescript
// Lightweight validation without external deps
type Validator<T> = {
  parse: (input: unknown) => T;
};

function string(): Validator<string> {
  return {
    parse(input) {
      if (typeof input !== 'string') throw new Error('Expected string');
      return input;
    },
  };
}

function number(): Validator<number> {
  return {
    parse(input) {
      const n = Number(input);
      if (isNaN(n)) throw new Error('Expected number');
      return n;
    },
  };
}

function object<T extends Record<string, Validator<unknown>>>(
  shape: T
): Validator<{ [K in keyof T]: ReturnType<T[K]['parse']> }> {
  return {
    parse(input) {
      if (typeof input !== 'object' || input === null) {
        throw new Error('Expected object');
      }
      const result: Record<string, unknown> = {};
      for (const [key, validator] of Object.entries(shape)) {
        try {
          result[key] = validator.parse((input as Record<string, unknown>)[key]);
        } catch (e) {
          throw new Error(`${key}: ${(e as Error).message}`);
        }
      }
      return result as any;
    },
  };
}

function array<T>(itemValidator: Validator<T>): Validator<T[]> {
  return {
    parse(input) {
      if (!Array.isArray(input)) throw new Error('Expected array');
      return input.map((item, i) => {
        try {
          return itemValidator.parse(item);
        } catch (e) {
          throw new Error(`[${i}]: ${(e as Error).message}`);
        }
      });
    },
  };
}

// Usage
const CreateUserSchema = object({
  name: string(),
  age: number(),
  tags: array(string()),
});

try {
  const user = CreateUserSchema.parse(JSON.parse(requestBody));
  // user is typed: { name: string; age: number; tags: string[] }
  console.log(user.name);
} catch (e) {
  console.error('Validation error:', (e as Error).message);
}

Use Cases

  • API request body validation
  • Type-safe input parsing without dependencies
  • Configuration file validation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.