Counterfact turns an OpenAPI spec into a stateful, TypeScript-native mock server in one command. Every endpoint is a .ts file you own and can edit live — with type safety, hot reload, and an interactive REPL — so your team can build against a real-feeling API before the backend exists.
npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json apiRequires Node ≥ 22.0.0
A complete, worked Petstore implementation is available at counterfact/example-petstore.
| The old way | With Counterfact |
|---|---|
| Wait for the backend to be ready | Start building against a live mock today |
| Hardcode fake data that breaks silently | Get type-safe responses generated from your spec |
| Restart the server every time you change something | Hot-reload keeps state while you iterate |
| Write a script to seed test data | Use the REPL to manipulate state interactively |
| Mock a few endpoints, ignore the rest | Every endpoint works out of the box |
| Return dumb static payloads | Write real logic — validate input, persist data, enforce business rules |
Every generated route returns random, schema-valid data out of the box. No editing needed to get started.
// api/routes/pet/{petId}.ts — generated, yours to edit
export const GET: HTTP_GET = ($) => {
return $.response[200].random();
};Your spec defines the contract. TypeScript enforces it. Autocomplete tells you exactly what response shapes are valid, and JSDoc comments — pulled straight from your OpenAPI descriptions — appear inline as you type.
export const GET: HTTP_GET = ($) => {
return $.response[200].json({ id: $.path.petId, name: "Fluffy" });
// ^ hover over `id` or `name` to see the JSDoc from your spec
};Create a _.context.ts file to share in-memory state across routes. Because state lives in the same process as the server it's lightning-fast, scalability doesn't matter (you're the only one using it), and restarting the server resets everything to a known clean state — no leftover data between test runs.
// api/routes/_.context.ts
export class Context {
private pets = new Map<number, Pet>();
private nextId = 1;
add(data: Omit<Pet, "id">): Pet {
const pet = { ...data, id: this.nextId++ };
this.pets.set(pet.id, pet);
return pet;
}
get(id: number): Pet | undefined { return this.pets.get(id); }
list(): Pet[] { return [...this.pets.values()]; }
remove(id: number): void { this.pets.delete(id); }
}// api/routes/pet.ts
export const GET: HTTP_GET = ($) => $.response[200].json($.context.list());
export const POST: HTTP_POST = ($) => $.response[200].json($.context.add($.body));
// api/routes/pet/{petId}.ts
export const GET: HTTP_GET = ($) => {
const pet = $.context.get($.path.petId);
return pet ? $.response[200].json(pet) : $.response[404].text(`No pet ${$.path.petId}`);
};Save a route file. The handler updates instantly. Your in-memory context survives every reload.
Save the OpenAPI spec. Your types update instantly and changes show up in your IDE.
A JavaScript prompt connected directly to your running server. Inspect state, fire requests, trigger edge cases — without touching a file.
⬣> context.list()
[ { id: 1, name: 'Fluffy', status: 'available' } ]
⬣> context.add({ name: 'Rex', photoUrls: [], status: 'pending' })
{ id: 2, name: 'Rex', status: 'pending', photoUrls: [] }
⬣> client.get("/pet/2")
{ status: 200, body: { id: 2, name: 'Rex', ... } }
Mock the paths that you want to control. Forward the rest to the real API. Toggle individual paths at runtime from the REPL.
npx counterfact@latest openapi.yaml api --proxy-url https://api.example.com⬣> .proxy on /payments # /payments/* → real API
⬣> .proxy off # all paths → mock
- Frontend teams building against an unfinished backend
- API-first teams who design the contract before writing code
- QA engineers who need to simulate edge cases, failure modes, and stateful scenarios on demand
- Developers writing integration or end-to-end tests against a real HTTP server
- AI agents that call third-party APIs — avoid rate limits and outages by running locally against a full-fidelity mock
- Developers exploring new APIs — experiment freely before you have a signed contract, test credentials, or production access
| Counterfact | json-server | WireMock | Prism | Microcks | |
|---|---|---|---|---|---|
| OpenAPI-native | ✅ | ❌ | Partial | ✅ | ✅ |
| Type-safe handlers | ✅ TypeScript | ❌ | ❌ | ❌ | ❌ |
| Real logic in handlers | ✅ | Limited | Via templating | ❌ | Via scripts |
| Hot reload | ✅ state-preserving | ❌ | ❌ | ❌ | ❌ |
| In-memory state | ✅ shared Context | ✅ flat JSON | ❌ | ❌ | ❌ |
| Interactive REPL | ✅ | ❌ | ❌ | ❌ | ❌ |
| Hybrid proxy | ✅ per-path | ❌ | ✅ | ✅ | ✅ |
| Request validation | ✅ | ❌ | ✅ | ✅ | ✅ |
| Automated test use | ✅ real HTTP server | ✅ | ✅ | ✅ | ✅ |
| Zero config | ✅ one command | ✅ | ❌ | ❌ | ❌ |
See How it compares for a full breakdown.
| Getting started | Step-by-step walkthrough from zero to a stateful mock |
| Reference | $ parameter, response builder, CLI flags, architecture |
| FAQ | Common questions about state, types, regeneration, and more |
| How it compares | Side-by-side with json-server, WireMock, Prism, Microcks, MSW |
| Petstore example | A complete worked example: the Swagger Petstore, fully implemented |
| Usage guide | Full documentation |
| Changelog | What's changed |
| Contributing | How to help |
