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:
- Customize freely — Add interceptors, modify error handling, extend the client
- Stay in sync — Regenerate types without losing your changes
- Debug easily — Inspect the cached spec and cache metadata
File Structure Overview
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:
| File | Purpose |
|---|---|
_internal/.api-cache.json | Cache metadata (hash, timestamp) |
_internal/openapi.json | Cached OpenAPI spec |
_generated/api.types.ts | TypeScript interfaces from schemas |
_generated/api.operations.ts | Operation 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:
| File | Purpose |
|---|---|
api.client.ts | Main typed API client |
api.instance.ts | Axios instance with interceptors |
api.error.ts | Error types and Result handling |
api.helpers.ts | Type 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 definitionscomponents- Schema definitionsoperations- Operation type definitions
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:
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:
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:
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:
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:
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:
// 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:
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:
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:
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 --forceThis will overwrite your customizations. Back up any changes first!