Routes

Each file in routes/ corresponds to an API path. For example, /users/{userId} maps to routes/users/{userId}.ts. The root path / maps to routes/index.ts.

A freshly generated route file looks like this:

export const GET: HTTP_GET = ($) => {
  return $.response[200].random();
};

export const POST: HTTP_POST = ($) => {
  return $.response[200].random();
};

Each exported function handles one HTTP method. The single argument $ gives you everything you need: request data, response builders, server state, and utilities.

[!TIP] If you know Express, think of $ as a type-safe combination of req and res.

Building responses with $.response

$.response is a fluent builder for HTTP responses. Start by picking a status code, then chain one or more methods:

MethodDescription
.random()Returns random data generated from the OpenAPI schema (uses examples where available)
.example(name)Returns a specific named example from the OpenAPI spec
.json(content)Returns a JSON body (also converts to XML automatically when the client requests it)
.text(content)Returns a plain-text body
.html(content)Returns an HTML body
.xml(content)Returns an XML body
.match(contentType, content)Returns a body with an explicit content type; chain multiple for content negotiation
.header(name, value)Adds a response header
return $.response[200].header("x-request-id", "abc123").json({ ok: true });

Setting cookies

Use .cookie(name, value, options?) to set one or more cookies. Each call appends a new Set-Cookie header and returns the builder for chaining.

return $.response[200]
  .cookie("sessionId", "abc123", {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: 3600,
  })
  .json({ ok: true });

Multiple cookies:

return $.response[200]
  .cookie("sessionId", "abc123")
  .cookie("theme", "dark")
  .json({ ok: true });

Supported options:

OptionDescription
pathCookie path (e.g. "/")
domainCookie domain
maxAgeMax age in seconds (Max-Age)
expiresExpiry Date object (Expires)
httpOnlySets the HttpOnly flag when true
secureSets the Secure flag when true
sameSite"lax", "strict", or "none" (SameSite)

Using named examples

If your OpenAPI spec defines named examples for a response, you can return a specific one by name using .example(). The name is autocompleted and type-checked — passing an unknown name is a compile error.

// Return a specific named example
return $.response[200].example("successResponse");

// Chain additional decorations after selecting an example
return $.response[200]
  .example("successResponse")
  .header("x-request-id", "abc123");

[!TIP] Your IDE’s autocomplete knows which status codes, headers, and response shapes are valid for each endpoint — based directly on your OpenAPI spec. If you omit a required header, TypeScript will tell you. When the spec changes and types are regenerated, TypeScript will surface any mismatches.

Reading request data

The request is exposed through four typed properties:

PropertyContents
$.pathPath parameters (e.g. $.path.userId for /users/{userId})
$.queryQuery string parameters
$.headersRequest headers
$.bodyRequest body
export const GET: HTTP_GET = ($) => {
  if ($.headers["x-token"] !== "super-secret") {
    return $.response[401].text("Unauthorized");
  }

  const content =
    `Results for "${$.query.keyword}" in ${$.path.groupName}` +
    ` with tags: ${$.body.tags.join(", ")}`;

  return $.response[200].text(content);
};

All four objects are typed from your OpenAPI spec, so autocomplete works for parameter names and values.

Object query parameters (exploded form style)

When a query parameter has type: object in the spec, OpenAPI’s default serialisation is exploded form style: each property of the object becomes its own query parameter.

For example, a pageable parameter of type Pageable (with page, size, and sort properties) is sent like this:

GET /items?page=0&size=100&sort=name

Counterfact automatically assembles these individual query parameters back into the object, so you can access them as:

export const GET: HTTP_GET = ($) => {
  const { page, size, sort } = $.query.pageable;
  // page = "0", size = "100", sort = "name"
  // ...
};

This behaviour follows the OpenAPI 3.x defaults (style: form, explode: true) and also applies when those values are set explicitly. To opt out, set explode: false on the parameter in your spec.

Basic auth: $.auth

When a request includes HTTP Basic credentials, they’re available at $.auth.username and $.auth.password.

Support for other security schemes (API key, OAuth 2, OpenID Connect, mutual TLS) is planned. Open an issue to help prioritize.

Simulating latency: $.delay()

Counterfact responds much faster than a real server. To test loading states and timeouts, use $.delay():

// pause for exactly one second
await $.delay(1000);

// pause for a random duration between 1 and 5 seconds
await $.delay(1000, 5000);

Escaping the type system: $.x

Counterfact translates your OpenAPI spec into strict TypeScript types. If your spec is incomplete, or you need to return something outside the spec (like a 500 error that isn’t documented), the strict types can get in the way.

$.x is an alias for $ with all types widened to any, giving you an escape hatch:

export const GET: HTTP_GET = ($) => {
  // header not defined in OpenAPI spec
  $.headers["my-undocumented-header"]; // TypeScript error
  $.x.headers["my-undocumented-header"]; // ok

  // status code not defined in OpenAPI spec
  return $.response[500].text("Error!"); // TypeScript error
  return $.x.response[500].text("Error!"); // ok
};

See also