Service Layer Pattern

The backend follows a strict Routes Services Database architecture. Routes are thin HTTP handlers; all business logic lives in services.

Architecture

Route (validation + HTTP) -> Service (business logic) -> Database (Drizzle ORM)

Routes handle: request validation, HTTP status codes, response formatting. Services handle: business rules, data transformations, cross-entity operations.

Service Directory

All services live in backend/src/services/ (46 files). Each service has a matching interface in backend/src/interfaces/ (48 files including infrastructure interfaces).

ServiceResponsibility
buildings-service.tsBuilding CRUD, geometry, energy data
quote-service.tsQuote lifecycle, line items, pricing
hubspot/CRM sync — see HubSpot Integration
portal/External portal logic — see Portal
document-template/PDF template management — see PDF Templates
feedback/User feedback collection — see Feedback System
create-services.tsDI container assembly — see Dependency Injection

Route Service Access (MANDATORY)

Routes access services via Hono context. Always destructure directly:

const { documentRequests, portal } = c.var.services;
 
// NEVER alias:
// const requestService = c.var.services.documentRequests;

For infrastructure/cross-cutting concerns, use c.var.container:

const { infrastructure, crossCutting } = c.var.container;

Service Reuse

Services are injected via Dependency Injection, making them reusable across:

  • Routes — HTTP request handlers
  • Background Jobs — BullMQ processors
  • Other services — cross-service orchestration

Interface Contract

Every service implements a corresponding interface from backend/src/interfaces/:

// backend/src/interfaces/buildings-service.ts
export interface IBuildingsService {
  getBuilding(id: string): Promise<Building>;
  createBuilding(data: CreateBuildingInput): Promise<Building>;
}

This enables testability and loose coupling.

See Also