Author: Lucas Sahdo Date: October 2025
Backend system for splitting expenses among group members using Clean Architecture + DDD, built with NestJS + TypeScript + AWS.
OnTrack is a backend system designed to split expenses among group members. It demonstrates Clean Architecture principles combined with Domain-Driven Design (DDD), implementing 6 bounded contexts to handle different aspects of the business domain.
- Group & Member Management - Create and manage groups with multiple members
- Expense Recording - Record expenses with automatic split calculation
- Balance Calculation - Calculate who owes whom in each group
- Debt Settlement - Track debt payments between members
- CSV Batch Upload - Process bulk expenses via S3
- Event-driven Notifications - Send email notifications via SNS/SQS
- Runtime: Node.js 23.11
- Language: TypeScript
- Framework: NestJS
- Database: PostgreSQL with TypeORM
- Cloud: AWS (S3, SNS, SQS)
- Testing: Jest
- Validation: class-validator, class-transformer
The project follows Clean Architecture principles with clear separation of concerns:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β (Controllers, DTOs, Middleware) β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β depends on
βββββββββββββββββββΌββββββββββββββββββββββββββββ
β Application Layer β
β (Use Cases, Ports/Interfaces, DTOs) β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β depends on
βββββββββββββββββββΌββββββββββββββββββββββββββββ
β Domain Layer β
β (Entities, Events, Business Rules) β
β β
Zero External Dependencies β
βββββββββββββββββββ²ββββββββββββββββββββββββββββ
β implemented by
βββββββββββββββββββ΄ββββββββββββββββββββββββββββ
β Infrastructure Layer β
β (Database, AWS, External Services) β
βββββββββββββββββββββββββββββββββββββββββββββββ
Key Principle: The Domain layer has zero external dependencies and contains pure business logic.
The system is organized into 6 bounded contexts:
- Aggregate: Group (with Members)
- Responsibility: Manage groups and membership
- Events:
GroupCreated,MemberAddedToGroup
- Aggregate: Expense (with ExpenseSplits)
- Responsibility: Record expenses, calculate splits
- Events:
ExpenseRecorded,BatchExpensesProcessed - Domain Service: SplitCalculator
- Aggregate: Balance (read model)
- Responsibility: Calculate member balances
- Events:
BalanceCalculated - Domain Service: BalanceCalculator
- Aggregate: Settlement
- Responsibility: Track debt payments
- Events:
DebtSettled
- Responsibility: Send email notifications
- Consumes:
ExpenseRecorded,DebtSettledevents
- Responsibility: CSV upload and processing
- Events:
CSVUploadStarted,CSVProcessingCompleted
core/
βββ src/
β βββ presentation/ # Presentation Layer
β β βββ group-management/
β β β βββ controllers/
β β β βββ dto/
β β βββ expense-management/
β β βββ balance/
β β βββ settlement/
β β βββ file-processing/
β β βββ shared/
β β βββ filters/ # Exception filters
β β βββ interceptors/ # Response interceptors
β β βββ middleware/ # HTTP middleware
β β
β βββ application/ # Application Layer
β β βββ group-management/
β β β βββ use-cases/ # Application use cases
β β β βββ ports/ # Interfaces/contracts
β β β βββ dto/ # Data transfer objects
β β βββ expense-management/
β β βββ balance/
β β βββ settlement/
β β βββ notification/
β β βββ file-processing/
β β βββ shared/
β β βββ interfaces/
β β βββ exceptions/
β β
β βββ domain/ # Domain Layer (Pure Business Logic)
β β βββ group-management/
β β β βββ entities/ # Domain entities
β β β βββ events/ # Domain events
β β β βββ repositories/ # Repository interfaces
β β β βββ services/ # Domain services
β β βββ expense-management/
β β βββ balance/
β β βββ settlement/
β β βββ notification/
β β βββ file-processing/
β β βββ shared/
β β βββ events/ # Shared domain events
β β βββ interfaces/ # Shared interfaces
β β βββ value-objects/ # Value objects
β β βββ exceptions/ # Domain exceptions
β β
β βββ infrastructure/ # Infrastructure Layer
β β βββ group-management/
β β β βββ repositories/ # Repository implementations
β β β βββ modules/ # NestJS modules
β β βββ expense-management/
β β βββ balance/
β β βββ settlement/
β β βββ notification/
β β β βββ adapters/ # Email service adapters
β β βββ file-processing/
β β β βββ adapters/ # S3 adapters
β β βββ database/
β β β βββ entities/ # TypeORM entities
β β β βββ migrations/ # Database migrations
β β β βββ seeds/ # Database seeders
β β βββ aws/
β β β βββ s3/ # S3 service
β β β βββ sns/ # SNS service
β β β βββ sqs/ # SQS service
β β βββ shared/
β β βββ config/
β β βββ modules/
β β
β βββ config/ # Configuration files
β β βββ app.config.ts
β β βββ database.config.ts
β β βββ aws.config.ts
β β
β βββ app.module.ts # Main application module
β βββ main.ts # Application entry point
β
βββ test/
β βββ unit/ # Unit tests
β βββ integration/ # Integration tests
β βββ e2e/ # End-to-end tests
β
βββ .env.example # Environment variables template
βββ package.json
βββ tsconfig.json
βββ README.md
- Docker (version 20.10+)
- Docker Compose (version 2.0+)
- Make (optional, but recommended)
- Clone the repository:
git clone <repository-url>
cd ontrack- Make scripts executable (first time only):
chmod +x docker/scripts/*.sh- Start everything with one command:
make goThis will:
- Stop any running containers
- Start PostgreSQL, Redis, and Backend services
- Install dependencies
- Run database migrations
- Start the development server with hot reload
The API will be available at http://localhost:3000
# Quick start (full setup)
make go
# Start services
make up
# Stop services
make down
# View logs
make logs # All services
make logs-backend # Backend only
# Access shells
make sh # Backend shell
make db-sh # PostgreSQL shell
make redis-sh # Redis CLI
# Database operations
make db-migrate # Run migrations
make db-migrate-gen # Generate new migration
make db-seed # Seed database
make db-reset # Reset database (β οΈ destroys data)
# Testing
make test # Run tests
make test-cov # Run with coverage
make test-e2e # E2E tests
# Code quality
make lint # Run linter
make format # Format code
# Help
make help # Show all commandsIf you prefer not to use Make:
# Start services
docker-compose up -d
# Install dependencies
docker-compose exec backend npm install
# Run migrations
docker-compose exec backend npm run migration:run
# View logs
docker-compose logs -f backendEnvironment variables are pre-configured in .env.docker with defaults for local development.
To customize:
cp .env.docker .env
# Edit .env with your configurationIf you prefer to run without Docker:
- Install dependencies:
cd apps/core
npm install-
Set up PostgreSQL and Redis locally
-
Configure
.envfile -
Run migrations:
npm run migration:run- Start the application:
npm run start:dev# Run all tests
make test
# Run tests in watch mode
make test-watch
# Run tests with coverage
make test-cov
# Run e2e tests
make test-e2e
# Run specific test file
make test path/to/test.spec.ts# Run unit tests
docker-compose exec backend npm run test
# Run unit tests in watch mode
docker-compose exec backend npm run test:watch
# Run e2e tests
docker-compose exec backend npm run test:e2e
# Generate test coverage
docker-compose exec backend npm run test:covSee .env.example for all available configuration options:
- Application: PORT, NODE_ENV
- Database: DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_DATABASE
- AWS: AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
- S3: S3_BUCKET_NAME
- SNS: SNS_TOPIC_ARN
- SQS: SQS_NOTIFICATION_QUEUE_URL, SQS_CSV_PROCESSING_QUEUE_URL
- Email: EMAIL_FROM
API documentation will be available via Swagger at /api/docs when the application is running.
- Dependency Rule: Dependencies only point inward. Outer layers depend on inner layers, never vice versa.
- Domain Independence: Domain layer has zero external dependencies.
- Use Cases: Application layer orchestrates domain logic without implementing business rules.
- Interfaces: Use ports/interfaces to invert dependencies.
- Bounded Contexts: Keep contexts isolated with clear boundaries.
- Aggregates: Ensure consistency boundaries are respected.
- Domain Events: Use events for cross-context communication.
- Ubiquitous Language: Use business terminology in code.