chowbea-axios

Getting Started

Set up chowbea-axios in your project in under 5 minutes.

You have an OpenAPI spec and a frontend that deserves better than any. This guide walks the full path: spec → generate → call → unwrap → query → ship. The first three steps happen here; the rest lives in The Query Layer.

Under five minutes. Longer if your backend isn't running yet — we can't fix that.

Step 1: Initialize Your Project

Run init in your project root:

chowbea-axios init

You'll be prompted for:

  1. OpenAPI spec endpoint URL — where your API docs are served
  2. Output folder — where generated files go (default: app/services/api)
  3. Package manager — detected automatically, overridable

Init installs axios, adds api:* scripts to package.json, and scaffolds client files. One command, not a checklist.

What Gets Created

api.config.toml
package.json
api.client.ts
api.instance.ts
api.error.ts
api.helpers.ts

Generated output appears after your first fetch.

NPM Scripts Added

package.json
{
  "scripts": {
    "api:fetch": "chowbea-axios fetch",
    "api:generate": "chowbea-axios generate",
    "api:watch": "chowbea-axios watch",
    "api:status": "chowbea-axios status",
    "api:validate": "chowbea-axios validate",
    "api:diff": "chowbea-axios diff"
  }
}

Pair watch with your dev server:

{
  "scripts": {
    "dev:all": "concurrently \"npm run api:watch\" \"npm run dev\""
  }
}

dev:all = types regenerate while you work. Run it. Future you will not send thank-you notes, but you'll notice fewer "wait, did the API change?" moments.

Step 2: Fetch Your OpenAPI Spec

Start your API server, then:

npm run api:fetch

This command:

  1. Downloads your OpenAPI spec from the configured endpoint
  2. Caches it locally for change detection
  3. Generates TypeScript types, operations, and contracts

Generated File Structure

.api-cache.json
openapi.json
api.types.ts
api.operations.ts
api.contracts.ts
api.client.ts
api.instance.ts
api.error.ts
api.helpers.ts

Files in _internal/ and _generated/ are overwritten on each fetch. Don't edit them. Customizations go in api.client.ts, api.instance.ts, and your app code.

Import types from api.contracts.tsUserDto, CreateUserBody, and friends. Named interfaces, cmd+click navigation, request bodies distinct from entity DTOs. Helpers like ServerModel<"…"> exist for edge cases; contracts are the default.

Step 3: Call the API

Import the client and make a typed call:

Example usage
import { api } from "./app/services/api/api.client";
import type { UserDto } from "./app/services/api/_generated/api.contracts";

// Operation-based (preferred)
const { data, error } = await api.op.getUserById({ id: "123" });

if (error) {
  console.error(error.message);
  console.error(error.code);    // e.g., "NOT_FOUND", "UNAUTHORIZED"
  console.error(error.status);  // HTTP status code
  return;
}

// data matches UserDto from your spec
console.log(data.name);
console.log(data.email);

Path-based calls work too:

const { data, error } = await api.get("/users/{id}", { id: "123" });

POST Request with Body

import type { CreateUserBody } from "./app/services/api/_generated/api.contracts";

const body: CreateUserBody = {
  name: "John Doe",
  email: "john@example.com",
};

const { data, error } = await api.op.createUser(body);

if (error) {
  console.error(error.message);
  return;
}

console.log("Created user:", data.id);

Every call returns { data, error } — no thrown exceptions at the transport layer. Check error before using data.

Step 4: Unwrap and Query (Where Apps Actually Live)

Raw { data, error } in every component is how tech debt starts. The recommended stack:

api.contracts.ts  →  api.op  →  exec  →  TanStack
  1. Types from api.contracts.ts
  2. Calls via api.op.*
  3. Unwrap once in a query class (exec throws QueryError on failure)
  4. Expose queryOptions / hooks to components
// Sketch — full pattern in the query layer docs
protected async exec<T>(promise: Promise<Result<T>>): Promise<T> {
  const { data, error } = await promise;
  if (error) throw new QueryError(error);
  return data;
}

Components import hooks, not api.op directly. One cache, one error shape, one place to fix things when the spec changes.

The Query Layer — the path that ships.

Step 5: Watch Mode (Development)

During development, keep types in sync automatically:

npm run api:watch

Polls your API endpoint and regenerates when the spec changes. Combine with your dev server via dev:all (see Step 1).

Change a backend DTO, save, watch regenerates, TypeScript shows what broke. Fix it before merge. That's the whole philosophy in one workflow.

Next Steps

On this page