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:
paths:
/users/{id}:
get:
operationId: getUserById # <- This becomes api.op.getUserById()
parameters:
- name: id
in: path
required: true
responses:
200:
description: User foundGenerates:
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
operationIdnaming - 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
operationIdfields - 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 usersYou'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:
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 operationsReal-World Example
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);
},
};