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

Build an API Mock Server

Build a programmable API mock server with OpenAPI schema import, dynamic response generation, request recording, latency simulation, and stateful mocking for frontend-first development.

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

Skills stack · 4 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
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× 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

Pablo leads frontend at a 20-person company. Backend APIs are always 2 sprints behind — frontend developers wait idle or build against stale Postman mocks that diverge from reality. Hardcoded mock data doesn't test edge cases: what happens when the API returns an empty list, a 500 error, or a 3-second delay? Contract changes break frontend at integration. They need a mock server: import OpenAPI spec, generate realistic responses automatically, simulate errors and latency, record real API calls as fixtures, and update mocks when the spec changes.

Step 1: Build the Mock Server Engine

typescript
// src/mock/server.ts — Programmable API mock server with OpenAPI import and stateful responses
import { Redis } from "ioredis";
import { randomBytes } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface MockEndpoint {
  id: string;
  method: string;
  path: string;              // with path params: /users/:id
  responses: MockResponse[];
  activeResponseIndex: number;
  delay?: number;            // simulated latency in ms
  recordMode: boolean;       // record real requests as fixtures
  state: Record<string, any>;// stateful data for CRUD simulation
}

interface MockResponse {
  name: string;              // "success", "empty", "error", "slow"
  status: number;
  headers: Record<string, string>;
  body: any;
  probability?: number;      // for random response selection (0-1)
  condition?: string;        // JS expression to match: "req.query.page > 5"
}

interface MockConfig {
  endpoints: MockEndpoint[];
  globalDelay?: number;
  proxyTarget?: string;      // forward unmatched requests to real API
}

let config: MockConfig = { endpoints: [] };

// Import from OpenAPI spec
export async function importOpenAPI(spec: any): Promise<{ endpoints: number; generated: number }> {
  const endpoints: MockEndpoint[] = [];
  let generated = 0;

  for (const [path, methods] of Object.entries(spec.paths || {})) {
    for (const [method, operation] of Object.entries(methods as any)) {
      if (["get", "post", "put", "patch", "delete"].includes(method)) {
        const op = operation as any;
        const responses: MockResponse[] = [];

        for (const [statusCode, responseSpec] of Object.entries(op.responses || {})) {
          const respSpec = responseSpec as any;
          const schema = respSpec.content?.["application/json"]?.schema;
          const body = schema ? generateFromSchema(schema, spec.components?.schemas) : {};

          responses.push({
            name: `${statusCode} ${respSpec.description || ""}`.trim(),
            status: parseInt(statusCode),
            headers: { "Content-Type": "application/json" },
            body,
          });
          generated++;
        }

        // Add default error responses
        if (!responses.find((r) => r.status === 500)) {
          responses.push({ name: "server_error", status: 500, headers: { "Content-Type": "application/json" }, body: { error: "Internal Server Error" } });
        }

        endpoints.push({
          id: `mock-${randomBytes(4).toString("hex")}`,
          method: method.toUpperCase(),
          path: path.replace(/{(\w+)}/g, ":$1"),  // convert {id} to :id
          responses,
          activeResponseIndex: 0,
          recordMode: false,
          state: {},
        });
      }
    }
  }

  config.endpoints = endpoints;
  await redis.set("mock:config", JSON.stringify(config));
  return { endpoints: endpoints.length, generated };
}

// Generate realistic data from JSON schema
function generateFromSchema(schema: any, components?: Record<string, any>): any {
  if (schema.$ref) {
    const refName = schema.$ref.split("/").pop();
    return generateFromSchema(components?.[refName] || {}, components);
  }

  switch (schema.type) {
    case "string":
      if (schema.format === "email") return `user-${randomBytes(3).toString("hex")}@example.com`;
      if (schema.format === "date-time") return new Date().toISOString();
      if (schema.format === "uuid") return randomBytes(16).toString("hex").replace(/(........)(....)(....)(....)(............)/, "$1-$2-$3-$4-$5");
      if (schema.enum) return schema.enum[0];
      return schema.example || `${schema.description || "string"}-${randomBytes(2).toString("hex")}`;
    case "number": case "integer":
      return schema.example || Math.floor(Math.random() * (schema.maximum || 100));
    case "boolean":
      return schema.example ?? true;
    case "array":
      return Array.from({ length: 3 }, () => generateFromSchema(schema.items || {}, components));
    case "object": {
      const obj: Record<string, any> = {};
      for (const [key, propSchema] of Object.entries(schema.properties || {})) {
        obj[key] = generateFromSchema(propSchema, components);
      }
      return obj;
    }
    default:
      return null;
  }
}

// Handle incoming mock request
export async function handleRequest(
  method: string,
  path: string,
  req: { query: Record<string, string>; body: any; headers: Record<string, string>; params: Record<string, string> }
): Promise<{ status: number; headers: Record<string, string>; body: any }> {
  const endpoint = matchEndpoint(method, path);

  if (!endpoint) {
    // Proxy to real API if configured
    if (config.proxyTarget) {
      return await proxyRequest(config.proxyTarget, method, path, req);
    }
    return { status: 404, headers: {}, body: { error: "No mock configured for this endpoint" } };
  }

  // Record mode: save request as fixture
  if (endpoint.recordMode) {
    await redis.rpush(`mock:recorded:${endpoint.id}`, JSON.stringify({
      timestamp: new Date().toISOString(), method, path, query: req.query, body: req.body,
    }));
  }

  // Select response (conditional → probability → active index)
  let response = endpoint.responses[endpoint.activeResponseIndex];

  // Check conditional responses
  for (const r of endpoint.responses) {
    if (r.condition) {
      try {
        const fn = new Function("req", `return ${r.condition}`);
        if (fn(req)) { response = r; break; }
      } catch {}
    }
  }

  // Probability-based selection
  const probResponses = endpoint.responses.filter((r) => r.probability);
  if (probResponses.length > 0) {
    const rand = Math.random();
    let cumulative = 0;
    for (const r of probResponses) {
      cumulative += r.probability!;
      if (rand <= cumulative) { response = r; break; }
    }
  }

  // Simulate latency
  const delay = endpoint.delay || config.globalDelay || 0;
  if (delay > 0) await new Promise((r) => setTimeout(r, delay));

  // Interpolate path params into response body
  let body = JSON.parse(JSON.stringify(response.body));
  body = interpolateParams(body, req.params);

  // Track usage
  await redis.hincrby("mock:stats", `${method}:${path}`, 1);

  return { status: response.status, headers: response.headers, body };
}

function matchEndpoint(method: string, path: string): MockEndpoint | null {
  return config.endpoints.find((e) => {
    if (e.method !== method.toUpperCase()) return false;
    const pattern = e.path.replace(/:([\w]+)/g, "([^/]+)");
    return new RegExp(`^${pattern}$`).test(path);
  }) || null;
}

function interpolateParams(body: any, params: Record<string, string>): any {
  if (typeof body === "string") {
    return body.replace(/:([\w]+)/g, (_, key) => params[key] || `:${key}`);
  }
  if (Array.isArray(body)) return body.map((i) => interpolateParams(i, params));
  if (typeof body === "object" && body !== null) {
    const result: Record<string, any> = {};
    for (const [key, value] of Object.entries(body)) {
      result[key] = interpolateParams(value, params);
    }
    return result;
  }
  return body;
}

async function proxyRequest(target: string, method: string, path: string, req: any): Promise<{ status: number; headers: Record<string, string>; body: any }> {
  const resp = await fetch(`${target}${path}`, {
    method, headers: req.headers, body: method !== "GET" ? JSON.stringify(req.body) : undefined,
  });
  const body = await resp.json().catch(() => resp.text());
  return { status: resp.status, headers: {}, body };
}

// Configure specific endpoint behavior
export async function setEndpointResponse(endpointPath: string, method: string, responseName: string): Promise<void> {
  const endpoint = config.endpoints.find((e) => e.path === endpointPath && e.method === method);
  if (!endpoint) throw new Error("Endpoint not found");
  const idx = endpoint.responses.findIndex((r) => r.name === responseName);
  if (idx === -1) throw new Error("Response not found");
  endpoint.activeResponseIndex = idx;
  await redis.set("mock:config", JSON.stringify(config));
}

Results

  • Frontend unblocked — import OpenAPI spec, mock server running in 30 seconds; frontend develops against realistic responses while backend builds the real API
  • Edge cases tested — switch endpoint to "empty" response: frontend handles empty states; switch to "500": frontend shows error page; no real API needed for testing
  • Contract sync — when OpenAPI spec updates, re-import; mock responses regenerate; frontend discovers breaking changes before integration
  • Latency simulation — set 3s delay on payment endpoint; frontend implements loading state and timeout handling; catches UX issues early
  • Request recording — proxy mode records real API responses as fixtures; replay offline; deterministic tests from production-like data

Related use cases