API Layer Pattern

Typed API communication layer between frontend and backend.

Frontend API Organization

All API modules live in frontend/src/lib/api/ (38 files including core and types).

Core Instance

frontend/src/lib/api/core.ts provides the shared Axios instance with:

  • JWT token injection from auth store
  • Automatic token refresh on 401 responses
  • Request/response interceptors for error normalization
  • Base URL configuration per environment

Domain Modules

Each entity has a dedicated API module with typed request/response:

ModuleEntity
buildings.tsBuildings
projects.tsProjects
contacts.tsContacts
quotes.tsQuotes
invoices.tsInvoices
files.ts / files-api.tsFiles
funding.tsFunding Programs / Funding Applications
portal.tsPortal
workflow.tsWorkflows
document-obtaining.tsDocument Obtaining

Module Structure

// frontend/src/lib/api/buildings.ts
export const buildingsApi = {
  list: (projectId: string) =>
    api.get<Building[]>(`/api/projects/${projectId}/buildings`),
  get: (id: string) =>
    api.get<Building>(`/api/buildings/${id}`),
  create: (data: CreateBuildingInput) =>
    api.post<Building>('/api/buildings', data),
};

Backend Route Validation

Routes use OpenAPIHono for auto-generated OpenAPI specs. All inputs are validated with Zod schemas before reaching services:

app.openapi(route, async (c) => {
  const validated = c.req.valid('json'); // Zod-validated
  const { buildings } = c.var.services;
  return c.json(await buildings.create(validated));
});

Shared Types

Request/response types shared between frontend and backend live in Shared Layer:

  • shared/api-types.tsApiResponse<T>, ApiError, isApiError()
  • shared/types.ts — Entity types, LocalizedText
  • shared/validation-constants.ts — Field length limits, regex patterns

See Also