Introduction
CLI tool that generates type-safe Axios clients from OpenAPI specifications.
chowbea-axios generates fully-typed Axios clients from your OpenAPI specification. You get type safety, autocomplete, and Result-based error handling—without writing a single type definition.
The Problem
Working with REST APIs in TypeScript often means:
- Manual type definitions that drift out of sync with your backend
- No autocomplete for endpoints, parameters, or response shapes
- Try/catch everywhere to handle errors, with inconsistent error shapes
- Runtime surprises when the API changes and your types don't
You end up maintaining two sources of truth: your OpenAPI spec and your TypeScript types. They inevitably diverge.
The Solution
chowbea-axios reads your OpenAPI spec and generates everything you need:
- Types extracted directly from your spec—always in sync
- A typed client with autocomplete for every endpoint
- Normalized errors with a consistent shape across all calls
- Watch mode that regenerates when your spec changes
One command. Zero manual types. Your IDE knows your entire API.
How It Works
Understanding the internals helps you get the most out of chowbea-axios.
1. Configuration
Everything starts with api.config.toml, created when you run init. This file is the single source of truth for the CLI:
api_endpoint = "http://localhost:3000/docs/swagger/json"
poll_interval_ms = 10000
[output]
folder = "src/services/api"
[instance]
base_url_env = "VITE_API_URL"
timeout = 30000The config tells chowbea-axios where to fetch your spec, where to output files, and how to configure the Axios instance.
2. Fetching and Caching
When you run fetch, the CLI:
- Reads
api_endpointfrom your config (orspec_filefor local specs) - Downloads the OpenAPI JSON and stores it in
_internal/openapi.json - Computes a hash of the spec and saves it to
_internal/.api-cache.json
On subsequent runs, chowbea-axios compares the current spec hash against the cached one. If they match, generation is skipped entirely. This is why fetch is fast after the first run—no redundant work.
3. Type Generation
The generator parses your OpenAPI spec and extracts:
- Paths — Each endpoint becomes a typed method (
/users/{id}→api.get("/users/{id}", { id })) - Schemas — Types from
components.schemasbecome TypeScript interfaces inapi.types.ts - Operations — Each
operationIdbecomes a named method (getUserById→api.op.getUserById()) - Parameters — Path, query, and body params are extracted and typed
The result is two generated files:
api.types.ts— All TypeScript types from your specapi.operations.ts— Operation-based methods with full typing
The Flow
┌─────────────────────────────────────────────────────────────────────────┐
│ chowbea-axios fetch │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ Read api.config.toml │
│ (endpoint, output folder) │
└───────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ Fetch OpenAPI Spec │
│ (remote URL or local file) │
└───────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ Compute Spec Hash │
└───────────────────────────────┘
│
▼
┌──────────────────┐
│ Hash Changed? │
└──────────────────┘
│ │
No │ │ Yes
▼ ▼
┌─────────────────┐ ┌─────────────────────┐
│ Skip Generation │ │ Parse & Generate │
│ (use cached) │ │ │
└─────────────────┘ └─────────────────────┘
│
┌─────────────────────┴─────────────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ api.types.ts │ │ api.operations.ts │
│ (from schemas) │ │ (from operationIds) │
└─────────────────────────┘ └─────────────────────────┘If the spec hasn't changed, the entire generation step is skipped. If it has, only the _generated/ folder is overwritten—your customizations in api.client.ts and api.instance.ts are preserved.
Two Ways to Call Your API
chowbea-axios gives you two equivalent ways to make API calls. Both are fully typed—pick whichever reads better in your codebase.
Path-based — Reference endpoints by their URL path:
const { data, error } = await api.get("/users/{id}", { id: "123" });Operation-based — Reference endpoints by their operationId:
const { data, error } = await api.op.getUserById({ id: "123" });The path-based style is intuitive if you think in REST resources. The operation-based style is cleaner if your spec has well-named operationIds. Use both interchangeably.
Result-Based Errors
API calls return { data, error } instead of throwing exceptions:
const { data, error } = await api.get("/users/{id}", { id: "123" });
if (error) {
console.log(error.status); // HTTP status code
console.log(error.message); // Normalized message
return;
}
// data is fully typed here
console.log(data.name);No try/catch blocks. No wondering what shape the error might be. Every error has the same structure, whether it's a network failure, a 404, or a validation error.
This pattern makes error handling explicit and predictable. You always know exactly where errors are handled because you have to check for them.
What Gets Generated
Your OpenAPI spec is processed into these files:
The _generated/ folder is overwritten on each generation. The other files are created once and safe to modify—add interceptors, change the base URL, customize error handling.
Built for Developer Experience
- Self-healing — Auto-creates directories and guides you through setup
- Smart caching — Skips regeneration when your spec hasn't changed
- Retry logic — Network requests retry with exponential backoff
- Atomic writes — Generation never leaves files in a partial state
- Graceful shutdown — Watch mode preserves cache on interruption
Support the Project
If chowbea-axios helps you ship faster, consider giving it a ⭐ star on GitHub. It helps others discover the project and motivates continued development.