Counterfact instantly turns an OpenAPI/Swagge spec into a live, working API you can run locally.
Instead of waiting for a backend—or wiring up brittle mocks—it generates a server where every endpoint is backed by TypeScript code. Responses are valid by default, but fully customizable, and the system is stateful, interactive, and hot-reloading.
It’s not just a mock server.
It’s a controllable API environment you can shape in real time.
Built by Patrick McElhaney · Currently available for the right opportunity → https://patrickmcelhaney.org
npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json apiThat's it. Counterfact reads your OpenAPI spec, generates TypeScript route files in api/, and starts a mock server — all in one command. Point it at your own spec instead of the Petstore whenever you're ready.
Requires Node ≥ 17.0.0
- ⚡ Zero config — one command to generate and start a simulated api
- 🔒 Type-safe by default — route handlers are typed directly from your OpenAPI spec
- 🔄 Hot reload — edit route files while the server is running; state is preserved
- 🧠 State management — POST data and GET it back; share state across routes with context objects
- 🖥 Live REPL — inspect and modify server state from your terminal without touching files
- 🔀 Hybrid proxy — route some paths to the real API while mocking others
- 🎲 Smart random data — uses OpenAPI examples and schema metadata to generate realistic responses
- 📖 Built-in Swagger UI — browse and test your mock API in a browser automatically
- 🔌 Middleware support — add custom middleware with
_.middleware.tsfiles
- Generate — Counterfact reads your OpenAPI spec and creates a
routes/directory with a.tsfile for each path, plus atypes/directory with fully typed request/response interfaces. - Customize — Edit the route files to return exactly the data your frontend needs. The full power of TypeScript is at your disposal.
- Run — The server hot-reloads on every save. No restart, no lost state.
Generated route files return random, schema-valid responses immediately — no editing required.
// mock-api/routes/store/order/{orderID}.ts
import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
export const GET: HTTP_GET = ($) => {
return $.response[200].random();
};Replace .random() with .json() to return specific data. TypeScript (via your IDE's autocomplete) guides you to a valid response.
import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
import type { HTTP_DELETE } from "../../../types/paths/store/order/{orderId}.types.js";
export const GET: HTTP_GET = ($) => {
const orders: Record<number, Order> = {
1: { petId: 100, status: "placed" },
2: { petId: 999, status: "approved" },
3: { petId: 1234, status: "delivered" },
};
const order = orders[$.path.orderID];
if (order === undefined) return $.response[404];
return $.response[200].json(order);
};
export const DELETE: HTTP_DELETE = ($) => {
return $.response[200];
};If your OpenAPI spec defines named examples, use .example(name) to return a specific one. The name is autocompleted and type-checked from your spec:
export const GET: HTTP_GET = ($) => {
return $.response[200].example("successResponse");
};Use a _.context.ts file to share in-memory state across routes. POST data and GET it back, just like a real API.
// mock-api/routes/_.context.ts
export class Context {
pets: Pet[] = [];
addPet(pet: Pet) {
const id = this.pets.length;
this.pets.push({ ...pet, id });
return this.pets[id];
}
getPetById(id: number) {
return this.pets[id];
}
}// mock-api/routes/pet.ts
export const POST: HTTP_POST = ($) => {
return $.response[200].json($.context.addPet($.body));
};
// mock-api/routes/pet/{petId}.ts
export const GET: HTTP_GET = ($) => {
const pet = $.context.getPetById($.path.petId);
if (!pet) return $.response[404].text(`Pet ${$.path.petId} not found.`);
return $.response[200].json(pet);
};You can also interact with the context object using a REPL. It's like DevTools on the server side. (See "Live REPL" below.)
Save a route file and the server picks it up instantly — no restart, no lost state. Your in-memory context survives every reload.
The REPL gives you a JavaScript prompt connected directly to your running server. Inspect state, trigger edge cases, or adjust proxy settings without touching a file.
⬣> context.pets.length
3
⬣> context.addPet({ name: "Fluffy", photoUrls: [] })
⬣> client.get("/pet/3")
⬣> .proxy on /payments # forward /payments to the real API
⬣> .proxy off # stop all proxying
Mock the paths that aren't ready yet while forwarding everything else to the real backend. See Proxying for details.
npx counterfact@latest openapi.yaml mock-api --proxy-url https://api.example.comEvery route handler is typed to match your OpenAPI spec. When the spec changes, regenerating the types surfaces any mismatches at compile time — before they become bugs.
export const GET: HTTP_GET = ($) => {
return $.response[200]
.header("x-request-id", $.headers["x-request-id"])
.json({
id: $.path.userId,
});
};npx counterfact@latest [openapi.yaml] [destination] [options]| Option | Description |
|---|---|
--port <number> |
Server port (default: 3100) |
-o, --open |
Open browser automatically |
-g, --generate |
Generate route and type files |
-w, --watch |
Generate and watch for spec changes |
-s, --serve |
Start the mock server |
-r, --repl |
Start the interactive REPL |
--proxy-url <url> |
Forward all requests to this URL by default |
--prefix <path> |
Base path prefix (e.g. /api/v1) |
Run npx counterfact --help for the full list of options.
Counterfact came out of a pattern I kept seeing: teams are slowed down more by coordination than by code.
I’ve spent 25+ years building software and improving how engineering organizations operate across large enterprises, regulated industries, and complex systems. Most of that time, the real constraint wasn’t technology—it was dependency and coordination.
Counterfact is one way of removing that friction.
I’m currently available — not for long.
