@budgetbuddyde/api
v1.0.0
Published
   ;
// Fetch transactions
const [transactions, error] = await api.backend.transaction.getAll();
if (error) {
console.error('Error loading transactions:', error);
return;
}
console.log('Transactions:', transactions);CRUD Operations
All entity services provide standard CRUD operations:
// Create - Create new category
const [newCategory, createError] = await api.backend.category.create({
name: 'Groceries',
description: 'Supermarket purchases',
});
// Read - Fetch all categories
const [categories, getAllError] = await api.backend.category.getAll();
// Read - Fetch single category
const [category, getError] = await api.backend.category.getById('category-id');
// Update - Update category
const [updated, updateError] = await api.backend.category.updateById(
'category-id',
{ name: 'Groceries & Beverages' }
);
// Delete - Delete category
const [deleted, deleteError] = await api.backend.category.deleteById('category-id');Query Parameters
Many endpoints support query parameters for filtering and pagination:
// Fetch transactions with date filter
const [transactions, error] = await api.backend.transaction.getAll({
startDate: new Date('2024-01-01'),
endDate: new Date('2024-12-31'),
limit: 50,
offset: 0,
});Error Handling
The package uses a TResult<T, E> pattern for type-safe error handling:
import type { TResult } from '@budgetbuddyde/api';
async function loadBudgets() {
const [budgets, error] = await api.backend.budget.getAll();
// Check for errors
if (error) {
// TypeScript knows that 'budgets' is null here
if (error instanceof BackendError) {
console.error('Backend error:', error.statusCode, error.message);
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
}
return;
}
// TypeScript knows that 'budgets' is not null here
console.log('Budgets loaded:', budgets);
}Custom Request Configuration
For advanced usage, request options can be passed:
// With custom headers
const [data, error] = await api.backend.transaction.getAll(
undefined,
{
headers: {
'X-Custom-Header': 'value',
},
signal: abortController.signal, // AbortController for cancellation
}
);Development
Build
npm run buildCompiles TypeScript to JavaScript in the lib/ folder.
Tests
# Run tests once
npm test
# Run tests in watch mode
npm run test:watchCode Quality
# Biome Check (Linting + Formatting)
npm run check
# Auto-fix
npm run check:write
# Linting only
npm run lint
# Formatting only
npm run formatAPI Reference
Available Services
| Service | Description | Endpoint |
|---------|-------------|----------|
| api.backend.category | Category management | /api/category |
| api.backend.paymentMethod | Payment methods | /api/paymentMethod |
| api.backend.transaction | Transactions | /api/transaction |
| api.backend.recurringPayment | Recurring payments | /api/recurringPayment |
| api.backend.budget | Budget management | /api/budget |
Common Methods
All services inherit from EntityService and provide:
// Type signature simplified
class EntityService<CreatePayload, UpdatePayload, ...> {
getAll<Q>(query?: Q, config?: RequestInit): Promise<TResult<GetAllResult>>;
getById(id: string, config?: RequestInit): Promise<TResult<GetResult>>;
create(payload: CreatePayload, config?: RequestInit): Promise<TResult<CreateResult>>;
updateById(id: string, payload: UpdatePayload, config?: RequestInit): Promise<TResult<UpdateResult>>;
deleteById(id: string, config?: RequestInit): Promise<TResult<DeleteResult>>;
}Type Safety
The package leverages Zod for runtime validation combined with TypeScript for compile-time safety:
// Schema defines both runtime and compile-time types
const CategorySchema = z.object({
id: z.string(),
name: z.string(),
});
// TypeScript type is derived from schema
type TCategory = z.infer<typeof CategorySchema>;
// Runtime validation in services
const parsingResult = CategorySchema.safeParse(apiResponse);
if (!parsingResult.success) {
// Handle validation errors
}Project Structure
packages/api/
├── src/ # Source code
│ ├── api.ts # Main API class
│ ├── client.ts # Re-export of API
│ ├── error.ts # Custom error classes
│ ├── index.ts # Main export
│ │
│ ├── services/ # Service layer for backend communication
│ │ ├── entity.service.ts # Base service with CRUD operations
│ │ └── <name>.service.ts # Entity-specific operations
│ │
│ └── types/ # TypeScript types and schemas
│ ├── common.ts # Common type helpers (TResult, etc.)
│ ├── <name>.type.ts # Entity-specific operations
│ │
│ ├── interfaces/ # TypeScript interfaces
│ │ ├── query.interface.ts # Base query interfaces
│ │ └── <name>.interface.ts # Entity-specific interfaces
│ │
│ └── schemas/ # Zod validation schemas
│ ├── common.schema.ts # Common schemas
│ └── <name>.schema.ts # Entity-specific schemas
│
├── lib/ # Compiled JavaScript files (generated)
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── README.mdArchitecture Components
1. API Class (api.ts)
The central entry point that aggregates all service instances:
const api = new Api('https://backend-url');
// Access all services:
// - api.backend.category
// - api.backend.paymentMethod
// - api.backend.transaction
// - api.backend.recurringPayment
// - api.backend.budget2. Entity Service (services/entity.service.ts)
Abstract base class for all entity services with generic CRUD operations:
getAll(query?, config?)- Fetch all entitiesgetById(id, config?)- Fetch single entitycreate(payload, config?)- Create new entityupdateById(id, payload, config?)- Update entitydeleteById(id, config?)- Delete entity
Features:
- Automatic Zod validation of all responses
- Type-safe request/response handling
- Query parameter serialization
- Error handling and error types
- Request config merging
3. Specialized Services (services/*.service.ts)
Extend EntityService with domain-specific methods:
// Example: BudgetService with additional method
class BudgetService extends EntityService {
async getEstimatedBudget(): Promise<TResult<TEstimatedBudget>> {
// Budget-specific logic
}
}4. Type System (types/)
Schemas (types/schemas/)
Zod schemas for runtime validation:
import { z } from 'zod';
export const CategorySchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().optional(),
createdAt: z.string().datetime(),
});
export const GetAllCategoriesResponse = z.object({
data: z.array(CategorySchema),
total: z.number(),
});Types (types/*.type.ts)
TypeScript types, often derived from Zod schemas:
import type { z } from 'zod';
import { CategorySchema } from './schemas/category.schema';
export type TCategory = z.infer<typeof CategorySchema>;Common Types (types/common.ts)
Shared type helpers:
TResult<T, E>- Result type for error handlingTApiResponse<Schema>- API response helperTypeOfSchema<Schema>- Schema-to-type converter
Interfaces (types/interfaces/)
TypeScript interfaces for structure definitions:
export interface IBaseGetAllQuery {
limit?: number;
offset?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}5. Error Handling (error.ts)
Custom error classes for various error scenarios:
CustomError- Base error classApiError- General API errorsBackendError- HTTP status errors from backendResponseNotJsonError- Response is not valid JSON
try {
const response = await fetch(url);
if (!response.ok) {
throw new BackendError(response.status, response.statusText);
}
} catch (error) {
if (error instanceof BackendError) {
console.error('Backend returned:', error.statusCode);
}
}