[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an API Request Validator

Build an API Request Validator

Build a centralized API request validator with schema-based validation, custom rules, sanitization, error formatting, and request logging for consistent input handling across endpoints.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 3 skills

Avg quality 95/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Liam leads backend at a 20-person company with 80 API endpoints. Validation is inconsistent: some endpoints use Zod, some use manual if checks, some have no validation at all. An unvalidated endpoint accepted {"amount": -500} creating a negative charge. Error responses differ: some return {"error": "..."}, others {"errors": [...]}, and some just 400 with no body. SQL injection was found in a query parameter that wasn't sanitized. They need centralized validation: schema-based validation per endpoint, input sanitization, consistent error formatting, custom business rules, and audit logging of invalid requests.

Step 1: Build the Validator

typescript
import { z, ZodSchema, ZodError } from "zod";
import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);

interface ValidationRule { name: string; check: (value: any, context: any) => boolean | Promise<boolean>; message: string; }
interface ValidationResult { valid: boolean; errors: Array<{ field: string; message: string; code: string }>; sanitized: Record<string, any>; }

const SANITIZERS: Record<string, (value: any) => any> = {
  trim: (v) => typeof v === "string" ? v.trim() : v,
  lowercase: (v) => typeof v === "string" ? v.toLowerCase() : v,
  stripHtml: (v) => typeof v === "string" ? v.replace(/<[^>]*>/g, "") : v,
  escapeSQL: (v) => typeof v === "string" ? v.replace(/['"\\;]/g, "") : v,
  normalizeEmail: (v) => typeof v === "string" ? v.toLowerCase().trim().replace(/\+.*@/, "@") : v,
};

// Validate request against schema + custom rules
export async function validate(data: any, schema: ZodSchema, rules?: ValidationRule[], sanitize?: string[]): Promise<ValidationResult> {
  let sanitized = { ...data };

  // Apply sanitizers
  if (sanitize) {
    for (const key of Object.keys(sanitized)) {
      for (const s of sanitize) {
        if (SANITIZERS[s]) sanitized[key] = SANITIZERS[s](sanitized[key]);
      }
    }
  }

  const errors: ValidationResult["errors"] = [];

  // Schema validation
  try {
    sanitized = schema.parse(sanitized);
  } catch (e) {
    if (e instanceof ZodError) {
      for (const issue of e.issues) {
        errors.push({ field: issue.path.join("."), message: issue.message, code: issue.code });
      }
    }
  }

  // Custom business rules
  if (rules && errors.length === 0) {
    for (const rule of rules) {
      const passed = await rule.check(sanitized, {});
      if (!passed) errors.push({ field: "_business", message: rule.message, code: "business_rule" });
    }
  }

  // Log invalid requests for security monitoring
  if (errors.length > 0) {
    await redis.hincrby("validation:stats", "rejected", 1);
    // Detect potential attacks
    const suspicious = JSON.stringify(data);
    if (/<script/i.test(suspicious) || /union\s+select/i.test(suspicious) || /;\s*drop/i.test(suspicious)) {
      await redis.rpush("security:suspicious", JSON.stringify({ data: suspicious.slice(0, 500), errors, timestamp: new Date().toISOString() }));
    }
  } else {
    await redis.hincrby("validation:stats", "accepted", 1);
  }

  return { valid: errors.length === 0, errors, sanitized };
}

// Middleware: auto-validate request body/query/params
export function validationMiddleware(config: { body?: ZodSchema; query?: ZodSchema; params?: ZodSchema; rules?: ValidationRule[]; sanitize?: string[] }) {
  return async (c: any, next: any) => {
    const allErrors: ValidationResult["errors"] = [];

    if (config.body) {
      const body = await c.req.json().catch(() => ({}));
      const result = await validate(body, config.body, config.rules, config.sanitize);
      if (!result.valid) allErrors.push(...result.errors);
      else c.set("validatedBody", result.sanitized);
    }

    if (config.query) {
      const query = Object.fromEntries(new URL(c.req.url).searchParams);
      const result = await validate(query, config.query);
      if (!result.valid) allErrors.push(...result.errors);
      else c.set("validatedQuery", result.sanitized);
    }

    if (allErrors.length > 0) {
      return c.json({ error: { code: "VALIDATION_ERROR", message: "Request validation failed", details: allErrors } }, 400);
    }

    await next();
  };
}

// Common reusable schemas
export const CommonSchemas = {
  pagination: z.object({ page: z.coerce.number().int().min(1).default(1), limit: z.coerce.number().int().min(1).max(100).default(20) }),
  id: z.object({ id: z.string().uuid() }),
  email: z.string().email().max(255),
  password: z.string().min(8).max(128).regex(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, "Must contain uppercase, lowercase, and number"),
  amount: z.number().positive().max(999999.99),
  dateRange: z.object({ startDate: z.string().datetime(), endDate: z.string().datetime() }).refine((d) => new Date(d.endDate) > new Date(d.startDate), "End date must be after start date"),
};

Results

  • Negative charge impossible — z.number().positive() on amount field; rejected at validation layer; no business logic needed to check
  • Consistent error format — every endpoint returns {error: {code, message, details: [{field, message}]}} on 400; frontend renders errors uniformly
  • SQL injection blocked — escapeSQL sanitizer strips dangerous characters; suspicious patterns logged for security review; attack surface minimized
  • 80 endpoints standardized — middleware handles body/query/params validation; one-liner per endpoint instead of 20 lines of manual checks
  • Invalid request monitoring — dashboard shows 2.3% rejection rate; security team sees XSS/SQLi attempt patterns; proactive security

Related use cases