This document describes how Bruno files are transformed into typed API definitions and factory functions for type-safe API consumption in TypeScript applications.
For each domain (based on Bruno folder structure), the following files are generated:
src/apis/
├── users/
│ ├── api.ts # API factory with all endpoints
│ ├── apiDefinitions.ts # Type metadata for each endpoint
│ └── index.ts # Exports
├── products/
│ ├── api.ts
│ ├── apiDefinitions.ts
│ └── index.ts
Purpose: Provides typed API client functions grouped by domain.
Example:
export const usersApi = {
getProfile: async (params: { params?: Record<string, unknown> }): Promise<GetProfileResponse> => {
const res = await axiosInstance.get<GetProfileResponse>(`/users/profile`, { params: params?.params });
return res.data;
},
updateProfile: async (params: { data: UpdateProfileRequest }): Promise<UpdateProfileResponse> => {
const res = await axiosInstance.put<UpdateProfileResponse>(`/users/profile`, params.data);
return res.data;
},
};Type Safety Features:
- Full TypeScript type inference
- Compile-time parameter validation
- Response type checking
Purpose: Provides typed metadata about each API endpoint.
Example:
import type { GetProfileResponse, UpdateProfileRequest, UpdateProfileResponse } from './api';
export const usersApiDefinitions = {
getProfile: {
method: 'GET' as const,
path: '/users/profile' as const,
pathParams: {} as Record<string, never>,
queryParams: {} as Record<string, unknown>,
body: {} as Record<string, never>,
response: {} as GetProfileResponse,
},
updateProfile: {
method: 'PUT' as const,
path: '/users/profile' as const,
pathParams: {} as Record<string, never>,
queryParams: {} as Record<string, never>,
body: {} as UpdateProfileRequest,
response: {} as UpdateProfileResponse,
},
} as const;
export type UsersApiDefinitions = typeof usersApiDefinitions;Metadata Fields:
| Field | Type | Description |
|---|---|---|
method |
'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' |
HTTP method |
path |
string |
URL path template |
pathParams |
Type object | Path parameter types |
queryParams |
Type object | Query parameter types |
body |
Type object | Request body type |
response |
Type object | Response type |
Bruno File: GET /users/:userId/posts/:postId
Generated Type:
pathParams: {} as { userId: string | number; postId: string | number }For GET requests:
queryParams: {} as Record<string, unknown>For non-GET requests:
queryParams: {} as Record<string, never>For POST/PUT/PATCH with body:
body: {} as CreateUserRequestFor GET/DELETE or no body:
body: {} as Record<string, never>Generated from Bruno docs block:
response: {} as GetUserResponseIf no docs block:
response: {} as voidimport { usersApi } from '@/apis/users';
// GET request
const profile = await usersApi.getProfile({
params: { includeDetails: true }
});
// POST request
const newUser = await usersApi.createUser({
data: { name: 'John', email: 'john@example.com' }
});
// PUT request with path params
const updated = await usersApi.updatePost({
postId: 123,
data: { title: 'New Title' }
});import { usersApi } from '@/apis/users';
export async function fetchProfile() {
return usersApi.getProfile({});
}import { usersApiDefinitions } from '@/apis/users';
// Extract types
type ProfileResponse = typeof usersApiDefinitions.getProfile.response;
type UpdateRequest = typeof usersApiDefinitions.updateProfile.body;
// Runtime metadata
console.log(usersApiDefinitions.getProfile.method); // 'GET'
console.log(usersApiDefinitions.getProfile.path); // '/users/profile'- Parse Bruno Files: Extract API definitions from
.brufiles - Extract Metadata: Determine method, path, parameters, body, response
- Infer Types: Generate TypeScript types from docs JSON examples
- Generate Factory: Create typed API client functions
- Generate Definitions: Create type metadata exports
- Generate Index: Export all APIs from domain
// ✅ Correct usage
await usersApi.getProfile({ params: { page: 1 } });
// ❌ Type error: missing data parameter
await usersApi.createUser({});
// ❌ Type error: wrong parameter type
await usersApi.getProfile({ data: {} });const profile = await usersApi.getProfile({});
// profile is automatically typed as GetProfileResponse
profile.id; // ✅ OK
profile.username; // ✅ OK
profile.invalid; // ❌ Type errorGenerated files (api.ts, apiDefinitions.ts) are overwritten on each generation. Do not add custom logic to these files.
src/
├── apis/ # Generated (do not modify)
│ └── users/
│ ├── api.ts
│ └── apiDefinitions.ts
└── hooks/ # Custom hooks (safe to modify)
└── useAuth.ts
import { usersApiDefinitions } from '@/apis/users';
function getEndpointPath<T extends keyof typeof usersApiDefinitions>(
endpoint: T
) {
return usersApiDefinitions[endpoint].path;
}Previous version generated hooks and query keys. The new approach:
Before:
import { useGetProfile } from '@/apis/users';
const { data } = useGetProfile();After:
import { usersApi } from '@/apis/users';
const data = await usersApi.getProfile({});Benefits:
- Framework-agnostic API clients
- Easier testing (mock API functions directly)
- Clear separation between data fetching and business logic
src/generator/apiFactoryGenerator.ts- Generates API factorysrc/generator/apiDefinitionGenerator.ts- Generates type definitionssrc/generator/typeGenerator.ts- Infers TypeScript typessrc/parser/bruParser.ts- Parses Bruno files