chowbea-axios
Usage

Operation-Based API

Use semantic operation names instead of path templates.

If your OpenAPI spec defines operationId for endpoints, you can use the operation-based API for more semantic method names.

Basic Usage

Access operations via api.op:

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

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

// Use operation name:
const { data, error } = await api.op.getUserById({ id: "123" });

How It Works

The CLI generates operation functions from your OpenAPI spec's operationId fields:

OpenAPI spec
paths:
  /users/{id}:
    get:
      operationId: getUserById  # <- This becomes api.op.getUserById()
      parameters:
        - name: id
          in: path
          required: true
      responses:
        200:
          description: User found

Generates:

api.operations.ts
export const createOperations = (apiClient: any) => ({
  /**
   * Get user by ID
   * @operationId getUserById
   * @method GET
   * @path /users/{id}
   */
  getUserById: (
    pathParams: { id: string | number },
    config?: RequestConfig<"/users/{id}", "get">
  ): Promise<Result<ResponseData<"/users/{id}", "get">>> =>
    apiClient.get("/users/{id}", pathParams, config),
});

Operation Signatures

Operations are generated with appropriate parameters based on the endpoint:

GET with Path Params

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

GET with Query Params

// GET /users?limit=10&offset=0
const { data, error } = await api.op.listUsers({
  params: { limit: 10, offset: 0 },
});

POST with Body

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

POST with Body and Path Params

// POST /users/{id}/posts
const { data, error } = await api.op.createUserPost(
  { title: "Hello", content: "World" },
  { id: "123" }
);

DELETE with Path Params

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

JSDoc Comments

Generated operations include JSDoc comments from your OpenAPI spec:

/**
 * Get a user by their unique identifier
 * Returns the user object if found
 * 
 * @operationId getUserById
 * @method GET
 * @path /users/{id}
 */
getUserById: (pathParams: { id: string | number }) => ...

This provides inline documentation in your IDE.

When to Use Operations vs Paths

Use Operation-Based When:

  • Your spec has consistent operationId naming
  • You want more semantic, readable code
  • Your team prefers method names over path templates
// Semantic and readable
await api.op.createUser({ name: "John" });
await api.op.getUserById({ id: "123" });
await api.op.deleteUser({ id: "123" });

Use Path-Based When:

  • Your spec lacks operationId fields
  • You're exploring an unfamiliar API
  • You prefer explicit endpoint visibility
// Clear about what's being called
await api.post("/users", { name: "John" });
await api.get("/users/{id}", { id: "123" });
await api.delete("/users/{id}", { id: "123" });

Missing operationId

Operations are only generated for endpoints that have operationId defined. Without it:

paths:
  /users:
    get:
      # No operationId - won't generate api.op method
      summary: List users

You'll see a warning during generation:

⚠ Skipping operation without operationId (method=GET, path=/users)

The path-based API still works:

// Always available, even without operationId
await api.get("/users");

Listing Available Operations

Check api.operations.ts to see all available operations:

api.operations.ts
export const createOperations = (apiClient: any) => ({
  // User operations
  listUsers: (...) => ...,
  getUserById: (...) => ...,
  createUser: (...) => ...,
  updateUser: (...) => ...,
  deleteUser: (...) => ...,
  
  // Post operations
  listPosts: (...) => ...,
  getPostById: (...) => ...,
  // ...
});

Your IDE will also autocomplete available operations:

api.op.  // <- Autocomplete shows all available operations

Real-World Example

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

export const userService = {
  async getById(id: string) {
    const { data, error } = await api.op.getUserById({ id });
    if (error) throw new Error(error.message);
    return data;
  },

  async create(input: { name: string; email: string }) {
    const { data, error } = await api.op.createUser(input);
    if (error) throw new Error(error.message);
    return data;
  },

  async list(options?: { limit?: number }) {
    const { data, error } = await api.op.listUsers({
      params: options,
    });
    if (error) throw new Error(error.message);
    return data;
  },

  async delete(id: string) {
    const { error } = await api.op.deleteUser({ id });
    if (error) throw new Error(error.message);
  },
};

Next Steps

On this page