chowbea-axios
Advanced

Generated Files

Understanding the generated file structure and customization options.

When you run chowbea-axios init followed by fetch, the CLI generates a complete API client structure in your project. Understanding this structure is key to customizing behavior and extending functionality.

Why This Structure?

The file organization follows a clear separation of concerns:

  • Internal files (_internal/) — Caching and raw spec storage. Never edit these.
  • Generated files (_generated/) — TypeScript types and operations extracted from your spec. Overwritten on each generation.
  • Editable files (root level) — Your customization points. Created once, never overwritten.

This design means you can:

  1. Customize freely — Add interceptors, modify error handling, extend the client
  2. Stay in sync — Regenerate types without losing your changes
  3. Debug easily — Inspect the cached spec and cache metadata

File Structure Overview

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

How Files Relate

┌──────────────────────────────────────────────────────────────────┐
│                        Your Application                          │
└──────────────────────────────────────────────────────────────────┘


                    ┌───────────────────────┐
                    │     api.client.ts     │  ◀── You import this
                    │   (main entry point)  │
                    └───────────────────────┘
                          │           │
            ┌─────────────┘           └─────────────┐
            ▼                                       ▼
┌───────────────────────┐               ┌───────────────────────┐
│   api.instance.ts     │               │  _generated/          │
│   (axios config)      │               │  api.operations.ts    │
└───────────────────────┘               │  api.types.ts         │
            │                           └───────────────────────┘
            ▼                                       │
┌───────────────────────┐                          │
│    api.error.ts       │  ◀───────────────────────┘
│    api.helpers.ts     │
└───────────────────────┘

File Categories

Auto-Managed Files (Don't Edit)

These files are overwritten on every fetch or generate:

FilePurpose
_internal/.api-cache.jsonCache metadata (hash, timestamp)
_internal/openapi.jsonCached OpenAPI spec
_generated/api.types.tsTypeScript interfaces from schemas
_generated/api.operations.tsOperation functions from operationIds

Never edit files in _internal/ or _generated/. Your changes will be lost on the next generation.

Editable Files (Generated Once)

These files are only created if they don't exist:

FilePurpose
api.client.tsMain typed API client
api.instance.tsAxios instance with interceptors
api.error.tsError types and Result handling
api.helpers.tsType utility helpers

You can safely modify these files—they won't be overwritten.

Detailed File Descriptions

api.types.ts

Generated by openapi-typescript, contains:

  • paths - All API path definitions
  • components - Schema definitions
  • operations - Operation type definitions
_generated/api.types.ts
export interface paths {
  "/users": {
    get: {
      parameters: { query?: { limit?: number } };
      responses: { 200: { content: { "application/json": User[] } } };
    };
    post: {
      requestBody: { content: { "application/json": CreateUserInput } };
      responses: { 201: { content: { "application/json": User } } };
    };
  };
  // ...
}

export interface components {
  schemas: {
    User: { id: string; name: string; email: string };
    CreateUserInput: { name: string; email: string };
    // ...
  };
}

api.operations.ts

Contains operation functions for endpoints with operationId:

_generated/api.operations.ts
export const createOperations = (apiClient: any) => ({
  /**
   * List all users
   * @operationId listUsers
   * @method GET
   * @path /users
   */
  listUsers: (config?: RequestConfig<"/users", "get">) =>
    apiClient.get("/users", config),

  /**
   * Get user by ID
   * @operationId getUserById
   * @method GET
   * @path /users/{id}
   */
  getUserById: (
    pathParams: { id: string | number },
    config?: RequestConfig<"/users/{id}", "get">
  ) => apiClient.get("/users/{id}", pathParams, config),
  
  // ...
});

api.client.ts

The main API client that you import in your application:

api.client.ts
import { axiosInstance } from "./api.instance";
import { safeRequest } from "./api.error";
import { createOperations } from "./_generated/api.operations";

const api = {
  get<P extends Paths>(...) { /* ... */ },
  post<P extends Paths>(...) { /* ... */ },
  put<P extends Paths>(...) { /* ... */ },
  delete<P extends Paths>(...) { /* ... */ },
  patch<P extends Paths>(...) { /* ... */ },
  
  // Operation-based API
  get op() {
    return createOperations(this);
  },
};

export { api };

api.instance.ts

Axios instance with auth interceptor:

api.instance.ts
import axios from "axios";

export const tokenKey = "auth-token";

export const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  withCredentials: true,
  timeout: 30000,
});

// Auth interceptor
axiosInstance.interceptors.request.use((config) => {
  if (typeof window !== "undefined") {
    const token = localStorage.getItem(tokenKey);
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
  }
  return config;
});

api.error.ts

Error handling utilities:

api.error.ts
export interface ApiError {
  message: string;
  code: string;
  status: number | null;
  request: RequestContext;
  details?: unknown;
}

export type Result<T> =
  | { data: T; error: null }
  | { data: null; error: ApiError };

export async function safeRequest<T>(
  promise: Promise<AxiosResponse<T>>
): Promise<Result<T>> {
  try {
    const response = await promise;
    return { data: response.data, error: null };
  } catch (err) {
    return { data: null, error: createApiError(err) };
  }
}

api.helpers.ts

Type utilities for extracting types from the spec:

api.helpers.ts
export type ApiRequestBody<P extends Paths, M extends HttpMethod> = ...;
export type ApiResponseData<P extends Paths, M extends HttpMethod> = ...;
export type ServerModel<ModelName extends keyof components["schemas"]> = ...;
// ...

Customizing Editable Files

Custom Interceptors

Add response interceptors in api.instance.ts:

api.instance.ts
// Add response interceptor for logging
axiosInstance.interceptors.response.use(
  (response) => {
    console.log(`[API] ${response.config.method} ${response.config.url}`, response.status);
    return response;
  },
  (error) => {
    console.error(`[API Error]`, error.response?.status, error.message);
    return Promise.reject(error);
  }
);

Custom Token Handling

Modify the token retrieval logic:

api.instance.ts
axiosInstance.interceptors.request.use((config) => {
  // Custom: Read from a different source
  const session = sessionStorage.getItem("session");
  if (session) {
    const { accessToken } = JSON.parse(session);
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});

Custom Error Normalization

Extend error handling in api.error.ts:

api.error.ts
export function normalizeErrorMessage(error: unknown): string {
  // Add custom format handling
  if (error && typeof error === "object") {
    const e = error as Record<string, unknown>;
    
    // Your API's custom format
    if (e.errorMessage && typeof e.errorMessage === "string") {
      return e.errorMessage;
    }
  }
  
  // Fall back to default handling
  // ... existing code ...
}

Adding Custom Methods

Extend the API client in api.client.ts:

api.client.ts
const api = {
  // ... existing methods ...
  
  // Custom: Batch requests
  async batch<T>(requests: Promise<Result<T>>[]): Promise<Result<T>[]> {
    return Promise.all(requests);
  },
  
  // Custom: Health check
  async healthCheck() {
    return this.get("/health");
  },
};

Regenerating Editable Files

If you want to regenerate the editable files (reset to defaults):

chowbea-axios init --force

This will overwrite your customizations. Back up any changes first!

Next Steps

On this page