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:
Router → Controller → Service → Repository / Integration
- Routers — Hono routes with Zod-validated OpenAPI schemas. Each module registers routes via
app.openapi(). Security (Cognito auth) is applied per-route. - 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 — DynamoDB operations via the document client. 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 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 |
chat |
AI streaming chat with MCP tools |
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 | Path | Purpose |
|---|---|---|
api-handler.lambda.ts |
Core API routes | Authentication, hello-world, common |
chat-api-handler.lambda.ts |
Chat routes | AI streaming chat with long timeout |
cognito-trigger.lambda.ts |
Cognito triggers | 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 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 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 |
chat/ |
AI chat interface |
Each module follows a consistent internal structure — see 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 uses Redux Toolkit with the following patterns:
- Slices define reducers and actions for each feature
- Thunks handle async operations (API calls, side effects)
- Selectors provide derived state with memoization
- Persisted slices survive page refreshes via Redux Persist
API Integration
The frontend consumes the auto-generated API client from shared/ts/api/generated/. The client is wrapped in a service singleton (ApiClientService) that handles:
- Base URL configuration (switches per environment)
- Authentication token injection
- Error handling and retry logic
Usage pattern:
import { getApiClientService } from '@/main/services/service-container.service';
const client = getApiClientService().getClient();
const response = await client.helloWorld.getHelloWorld();
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.