API Layer Pattern
Typed API communication layer between frontend and backend.
Frontend API Organization
All API modules live in frontend/src/lib/api/ (53 modules excluding tests, as of 2026-06).
Core Instance
frontend/src/lib/api/core.ts provides the shared Axios instance with:
- Cookie credentials (
withCredentials: true) plus an in-memory access token store (setAccessToken) — the request interceptor attaches the Bearer token - Response envelope unwrapper (registered first), slow-API friction events (>5s), and 5xx Sentry capture
- Single 401 handler (registered second): refresh + retry, or redirect to
/loginif refresh fails — never add a second 401 redirect, it races the refresh - Base URL configuration per environment (
VITE_API_URL, default/api)
Domain Modules
Each entity has a dedicated API module with typed request/response:
| Module | Entity |
|---|---|
buildings.ts | Buildings |
projects.ts | Projects |
contacts.ts | Contacts |
quotes.ts | Quotes |
invoices.ts | Invoices |
files.ts / files-api.ts | Files |
funding.ts | Funding Programs / Funding Applications |
portal.ts | Portal |
workflow.ts | Workflows |
document-obtaining.ts | Document 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 are plain Hono with zValidator (used across 120+ route files, as of 2026-06). All inputs are validated with Zod schemas before reaching services:
app.post('/', zValidator('json', createBuildingSchema), async (c) => {
const validated = c.req.valid('json'); // Zod-validated
const { buildings } = c.var.services;
return c.json(await buildings.create(validated));
});OpenAPIHono is used only for health.ts and version.ts; full OpenAPI coverage was deliberately declined (I#1955, docs/decisions/1955-deliberate-passes.md).
Shared Types
Request/response types shared between frontend and backend live in Shared Layer:
shared/api-types.ts—ApiResponse<T>(success/data/error envelope + pagination),ApiError(code,message,details?),isApiError()shared/types.ts— Entity types,LocalizedTextshared/validation-constants.ts—VALIDATION_LIMITSfield length limitsshared/money.ts(@shared/money) — money values cross the wire as decimal strings, parsed withfromString()— see Validation Pattern
See Also
- React Query Pattern — queries that consume these API modules
- Validation Pattern — Zod schemas on both sides
- Frontend Architecture — frontend structure
- Backend Architecture — backend route organization
- Error Handling Pattern — API error contract
- Shared Layer — cross-boundary types