chowbea-axios
Usage

Using the API Client

Make type-safe API calls with the generated client.

The generated api.client.ts provides a fully-typed HTTP client with Result-based error handling.

Basic Usage

Import and use the API client:

Basic API calls
import { api } from "./app/services/api/api.client";

// GET request
const { data, error } = await api.get("/users");

// POST request with body
const { data, error } = await api.post("/users", {
  name: "John Doe",
  email: "john@example.com",
});

// PUT request
const { data, error } = await api.put("/users/{id}", 
  { name: "Jane Doe" },
  { id: "123" }
);

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

// PATCH request
const { data, error } = await api.patch("/users/{id}",
  { name: "Updated Name" },
  { id: "123" }
);

Path Parameters

For paths with parameters like /users/{id}, pass them as the last argument:

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

// Multiple path parameters
const { data, error } = await api.get("/users/{userId}/posts/{postId}", {
  userId: "123",
  postId: "456",
});

Path parameters are type-safe—TypeScript will error if you miss a required parameter:

// TypeScript error: missing 'id' property
const { data, error } = await api.get("/users/{id}");

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

Query Parameters

Pass query parameters via the config.params option:

const { data, error } = await api.get("/users", {
  params: {
    limit: 10,
    offset: 0,
    sort: "createdAt",
  },
});

Query parameters are typed based on your OpenAPI spec:

// If your spec defines:
// GET /users?limit=int&offset=int&search=string

const { data, error } = await api.get("/users", {
  params: {
    limit: 10,       // number
    offset: 0,       // number
    search: "john",  // string
  },
});

Request Bodies

For POST, PUT, and PATCH requests, the body type is inferred from your OpenAPI spec:

// Body type is inferred from your spec
const { data, error } = await api.post("/users", {
  name: "John Doe",      // Required
  email: "john@example.com", // Required
  bio: "Developer",      // Optional
});

TypeScript catches invalid bodies:

// TypeScript error: 'email' is required
const { data, error } = await api.post("/users", {
  name: "John Doe",
});

Response Types

Response data is fully typed based on your OpenAPI spec:

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

if (error) {
  return;
}

// data is typed based on your spec
console.log(data.id);       // Typed
console.log(data.name);     // Typed
console.log(data.email);    // Typed
console.log(data.unknown);  // TypeScript error

Result-Based Error Handling

All API calls return a Result type instead of throwing:

type Result<T> =
  | { data: T; error: null }       // Success
  | { data: null; error: ApiError }; // Failure

This makes error handling explicit:

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

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

// TypeScript knows data is not null here
console.log(data.name);

See Error Handling for detailed error handling patterns.

Additional Axios Config

Pass additional Axios configuration as the last argument:

// With timeout
const { data, error } = await api.get("/users", {
  timeout: 5000,
});

// With custom headers
const { data, error } = await api.get("/users", {
  headers: {
    "X-Custom-Header": "value",
  },
});

// With path params and config
const { data, error } = await api.get("/users/{id}", 
  { id: "123" },
  { timeout: 5000 }
);

Method Signatures

GET

// Without path params
api.get<Path>(url, config?)

// With path params
api.get<Path>(url, pathParams, config?)

POST

// Without path params
api.post<Path>(url, body, config?)

// With path params
api.post<Path>(url, body, pathParams, config?)

PUT

// Without path params
api.put<Path>(url, body, config?)

// With path params
api.put<Path>(url, body, pathParams, config?)

DELETE

// Without path params
api.delete<Path>(url, config?)

// With path params
api.delete<Path>(url, pathParams, config?)

PATCH

// Without path params
api.patch<Path>(url, body, config?)

// With path params
api.patch<Path>(url, body, pathParams, config?)

Real-World Example

User service
import { api } from "./api/api.client";

export async function getUser(id: string) {
  const { data, error } = await api.get("/users/{id}", { id });
  
  if (error) {
    if (error.code === "NOT_FOUND") {
      return null;
    }
    throw new Error(error.message);
  }
  
  return data;
}

export async function createUser(input: { name: string; email: string }) {
  const { data, error } = await api.post("/users", input);
  
  if (error) {
    if (error.code === "VALIDATION_ERROR") {
      throw new Error(`Validation failed: ${error.message}`);
    }
    throw new Error(error.message);
  }
  
  return data;
}

export async function listUsers(options?: { limit?: number; offset?: number }) {
  const { data, error } = await api.get("/users", {
    params: options,
  });
  
  if (error) {
    throw new Error(error.message);
  }
  
  return data;
}

Next Steps

On this page