Skip to content
Back to Project Tutorials

API Mock Server

Generate a mock API from OpenAPI spec with stateful responses, fault injection, and team sharing.

Design & Schema

Define mock routes from OpenAPI spec, choose delay and fault injection, and the storage model.

Why

Mock servers front-load contract testing. If frontend and backend code against the same OpenAPI mock, integration drops from weeks to days. Fault injection (timeouts, 500s) catches frontend error handling before production, where it costs 10x more to fix.

When

Before frontend and backend diverge. Generate mocks from the OpenAPI spec in week 1 of a new API.

How

1. Write or obtain the OpenAPI spec - Define every endpoint, request/response schema, and status code - Use examples in the schema for realistic mock data - Validate with redocly or openapi-cli lint 2. Generate the mock server - Prism: npx prism mock openapi.yaml -> instant mock on port 4010 - MSW (Mock Service Worker): for in-browser mocking in Storybook/tests - WireMock: for JVM-based projects or standalone Docker 3. Add dynamic data with Faker - Replace static examples with Faker-generated values for lists - GET /users -> array of 25 fake users with realistic names/emails - Seed Faker for reproducible test runs 4. Design fault injection - Delay: add 2s latency to /slow endpoint to test loading states - Errors: return 500 on /error to test error boundaries - Configurable via header: X-Mock-Status: 500 5. Plan the storage model - Stateless (default): every request returns fresh Faker data - Stateful: in-memory store so POST then GET returns the created item - Choose stateful for CRUD demos and integration tests

Example

# openapi.yaml (excerpt) paths: /users: get: responses: "200": content: application/json: schema: { $ref: "#/components/schemas/UserList" } example: - { id: 1, email: "[email protected]", role: "admin" } post: requestBody: content: application/json: schema: { $ref: "#/components/schemas/NewUser" } responses: "201": content: application/json: schema: { $ref: "#/components/schemas/User" } # Generate mock $ npx prism mock openapi.yaml [CLI] ✔ Prism is listening on http://localhost:4010 # GET /users -> 200 with the example array # POST /users -> 201 with a generated user

Common Problems

  • ⚠No OpenAPI spec — frontend and backend agree verbally, drift within a week, integration takes days to debug
  • ⚠Static examples only — GET /users always returns the same 3 users, hiding pagination and empty-state bugs
  • ⚠No fault injection — frontend never sees a 500 in dev, ships without an error boundary

Best Practices

  • ✓Generate the mock from the OpenAPI spec, not by hand — the spec is the single source of truth for both mock and server
  • ✓Use Faker for list endpoints so pagination, sorting, and empty states are testable with realistic volume
  • ✓Add a fault-injection header (X-Mock-Status: 500) so QA can trigger error paths without code changes

Implementation & Dynamic Responses

Build stateful mocks, dynamic data via Faker, and conditional routing for realistic demos and tests.

Why

Static mocks break when the frontend needs to test create-then-list flows. Stateful mocks (in-memory store) let CRUD sequences work end-to-end in Storybook and tests without a backend, catching integration bugs before they reach staging.

When

After the static mock serves the happy path. Add stateful routes for CRUD demos and integration tests.

How

1. Build an in-memory store - const db = { users: [], posts: [] } — reset on server restart - Seed with 25 Faker-generated records on boot - Persist to a JSON file if you need data to survive restarts 2. Wire CRUD to the store - GET /users -> db.users (with pagination: ?page&limit) - POST /users -> push to db.users, return 201 with new id - PUT /users/:id -> find and replace, 404 if missing - DELETE /users/:id -> filter out, 204 3. Add dynamic data generation - Faker.person.fullName(), Faker.internet.email() - Dates: Faker.date.recent({ days: 30 }) - Nested: a post has an author (reference a user from the store) 4. Conditional routing via headers - X-Mock-Delay: 2000 -> setTimeout 2s before responding - X-Mock-Status: 500 -> return 500 with error body - X-Mock-Empty: true -> return [] to test empty states 5. Integrate with the frontend test suite - MSW in Jest/Vitest: intercept fetch, return mock responses - Storybook: MSW addon so stories hit the mock, not a real API - Reset the store between test files to avoid cross-test pollution

Example

// Stateful mock with Express + Faker import express from "express"; import { faker } from "@faker-js/faker"; const app = express(); const db = { users: Array.from({ length: 25 }, (_, i) => ({ id: i + 1, name: faker.person.fullName(), email: faker.internet.email(), role: faker.helpers.arrayElement(["admin", "member"]), })) }; app.get("/users", (req, res) => { const { page = 1, limit = 10 } = req.query; const start = (page - 1) * limit; res.json({ data: db.users.slice(start, start + +limit), total: db.users.length }); }); app.post("/users", (req, res) => { const user = { id: db.users.length + 1, ...req.body }; db.users.push(user); res.status(201).json(user); }); // Fault injection via header app.use((req, res, next) => { if (req.headers["x-mock-status"]) { return res.status(+req.headers["x-mock-status"]).json({ error: "mock" }); } next(); });

Common Problems

  • ⚠Stateless mock for a CRUD demo — POST then GET returns stale data, demo looks broken
  • ⚠No store reset between tests — test A's created user leaks into test B, causing flaky failures
  • ⚠Faker without a seed — generated data changes every run, snapshot tests fail randomly

Best Practices

  • ✓Use a stateful in-memory store for CRUD endpoints so create-then-list flows work in demos and tests
  • ✓Reset the mock store between test files (beforeEach) to prevent cross-test data pollution
  • ✓Seed Faker with a fixed value (faker.seed(123)) in CI so generated data is reproducible

Deployment & Sharing

Deploy as a Docker image, npm package, or hosted service so the whole team shares one mock.

Why

A mock server only helps if the whole team uses the same one. A hosted/shared mock (Docker image or MSW committed to the repo) ensures frontend, mobile, and QA test against identical contracts, eliminating 'works on my machine' integration failures.

When

When the mock is stable for the happy path. Package it before frontend integration begins so there is one source of truth.

How

1. Package as a Docker image - FROM node:20-alpine, copy mock server, CMD prism mock openapi.yaml - Push to ghcr.io or Docker Hub with a semver tag - Team runs: docker run -p 4010:4010 ghcr.io/org/api-mock:1.2.0 2. Or commit MSW handlers to the repo - src/mocks/handlers.ts — committed, version-controlled - Frontend imports handlers in dev mode and tests - Update handlers in the same PR as the OpenAPI spec change 3. Deploy a shared hosted instance - Fly.io or Railway: deploy the Prism server, share the URL - QA and mobile point to this URL for manual testing - Protect with a simple API key header to avoid public exposure 4. Keep mock and spec in sync - CI step: regenerate mock from openapi.yaml on every PR - Fail the build if committed handlers diverge from the spec - Use spectral to lint the spec and block breaking changes 5. Document for the team - README: how to run locally, how to inject faults, how to reset - Add a /__reset endpoint to clear the stateful store between demos - List available X-Mock-* headers with examples

Example

# Dockerfile FROM node:20-alpine WORKDIR /app COPY openapi.yaml . RUN npm install -g @stoplight/prism-cli EXPOSE 4010 CMD ["prism", "mock", "openapi.yaml", "-h", "0.0.0.0"] # CI: regenerate mock + lint spec (GitHub Actions) - name: Lint OpenAPI run: npx @redocly/cli lint openapi.yaml - name: Check handlers in sync run: npx openapi-typescript openapi.yaml -o src/api/types.ts && git diff --exit-code src/api/types.ts # Shared instance on Fly.io $ fly deploy --image ghcr.io/org/api-mock:1.2.0 -> https://api-mock.fly.dev (share with QA + mobile team)

Common Problems

  • ⚠Everyone runs a different mock version — frontend tests pass, integration fails because contracts drifted
  • ⚠No CI check on spec-handler sync — handlers go stale, mock returns fields the real API no longer has
  • ⚠Shared mock exposed publicly without auth — someone discovers it and probes your API surface

Best Practices

  • ✓Commit MSW handlers to the repo and update them in the same PR as the OpenAPI spec — one source of truth
  • ✓Add a CI step that regenerates types from the spec and fails on diff — catches spec-handler drift automatically
  • ✓Protect any shared hosted mock with an API key header and never deploy it to a public domain without auth

Full Developer Lifecycle

A 14-stage developer roadmap from idea to maintenance — visualize dependencies and track progress.

Next Step