Architecture

This guide provides a high-level overview of the backend and frontend architecture.

Backend Architecture

Layered Pattern

The backend follows a layered architecture with constructor-based dependency injection:

Shared oRPC Contract → oRPC Router → Controller → Service → Repository / Integration
  • Contracts — Shared oRPC contracts and API models in packages/shared/api/ are the source of truth for backend, frontend, and API tests.
  • Routers — Module *.orpc-router.ts files implement shared contracts and delegate to controllers. Special non-standard transports, such as form redirects or streaming, may keep focused Hono routes.
  • Controllers — Compose service methods and extract auth context. Every controller has an interface and a mock implementation for testing.
  • Services — Business logic. Pure functions where possible. No direct HTTP or database access.
  • Repositories — Store-neutral data access behind a port interface, with two interchangeable implementations selected at runtime by PERSISTENCE: DynamoDB (*-ddb.repository.ts, via the document client) and TypeORM/Postgres (*-orm.repository.ts). Both speak the persistence-agnostic Zod model (*.schema.ts). Method naming convention: getXyz, findXyzByAbc, createXyz, updateXyz, upsertXyz, deleteXyz.
  • Integrations — External service clients (AWS Secrets Manager, Cognito, TimeBack API).

Dependency Injection (Container Pattern)

All classes receive dependencies through constructor parameters. Container functions in apps/backend/entrypoints/containers/ wire the full dependency graph (services → controllers → routers). See any container file for the pattern.

Modules

Module Purpose
authentication Sign-up, LTI launch, magic links
hello-world Example endpoint
common Shared utilities, docs router, error handling

Entry Points

The backend has multiple Lambda entry points, each with its own container:

Handler Purpose
authentication-api-handler.lambda.ts Authentication routes
hello-world-api-handler.lambda.ts Hello-world example routes
docs-api-handler.lambda.ts OpenAPI docs and Scalar viewer
cognito-trigger.lambda.ts Custom auth challenges (magic links)

Each handler bootstraps its own dependency container, keeping Lambda cold starts focused on only the required dependencies.

Local Development Server

The apps/backend/debug/dev.ts file composes all handlers into a single Hono app running on port 3001. It adds credential refresh (every 45 minutes), CORS headers, and the Scalar documentation viewer.

Frontend Architecture

Module Structure

The frontend is organized into feature modules under apps/frontend/src/:

Module Purpose
main/ Core infrastructure — routing, store, services, utilities
common/ Reusable components, hooks, and utilities
config/ Environment configuration management
user-management/ Authentication screens and user state

Each module follows a consistent internal structure — see apps/frontend/docs/module-definition/ for the full specification. Key conventions:

  • Screens are lazy-loaded route entry points
  • Slices use Redux Toolkit for state management
  • Services are singletons accessed via service-container.service.ts

State Management

The frontend separates server state from app/UI state:

  • TanStack Query owns contract-backed server state via getApiQueryUtils() and @orpc/react-query.
  • Redux slices keep app/UI state, persisted settings, environment selection, and local workflow flags.
  • Thunks remain for non-contract side effects such as Cognito redirects, environment config bootstrap, and app lifecycle orchestration.
  • Selectors provide derived state from Redux-owned state.

API Integration

ApiClientService builds one typed oRPC client per backend module. Each client uses the shared contract and the module's mounted Lambda prefix; the service also centralizes Cognito token injection and environment reinitialization.

Hooks use query utilities exposed by service-container.service.ts:

import { getApiQueryUtils } from '@/main/services/service-container.service';
import { useMutation } from '@tanstack/react-query';

const orpc = getApiQueryUtils();
const mutation = useMutation(orpc.authentication.signUp.mutationOptions());

Use raw clients only for rare imperative calls outside React hooks. Long-running work (e.g. studio agent turns) is surfaced by polling persisted run-state and message records, not by a streaming transport.

Environment Switching

The frontend supports runtime environment switching via the ?setEnv query parameter. Environment configs (API URLs, Cognito settings) are loaded from a JSON file hosted on S3, allowing the same frontend build to target any backend.