Skip to content

System for splitting expenses among group members using Clean Architecture + DDD, built with NestJS + TypeScript + AWS. Front in NextJs

Notifications You must be signed in to change notification settings

sahdoio/ontrack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

8 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

OnTrack - Peer-to-Peer Payment Splitter

Author: Lucas Sahdo Date: October 2025

Backend system for splitting expenses among group members using Clean Architecture + DDD, built with NestJS + TypeScript + AWS.

Overview

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.

Core Features

  • 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

Tech Stack

  • 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

Architecture

Clean Architecture (4 Layers)

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.


Bounded Contexts (DDD)

The system is organized into 6 bounded contexts:

1. Group Management Context

  • Aggregate: Group (with Members)
  • Responsibility: Manage groups and membership
  • Events: GroupCreated, MemberAddedToGroup

2. Expense Management Context

  • Aggregate: Expense (with ExpenseSplits)
  • Responsibility: Record expenses, calculate splits
  • Events: ExpenseRecorded, BatchExpensesProcessed
  • Domain Service: SplitCalculator

3. Balance Context

  • Aggregate: Balance (read model)
  • Responsibility: Calculate member balances
  • Events: BalanceCalculated
  • Domain Service: BalanceCalculator

4. Settlement Context

  • Aggregate: Settlement
  • Responsibility: Track debt payments
  • Events: DebtSettled

5. Notification Context

  • Responsibility: Send email notifications
  • Consumes: ExpenseRecorded, DebtSettled events

6. File Processing Context

  • Responsibility: CSV upload and processing
  • Events: CSVUploadStarted, CSVProcessingCompleted

Project Structure

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

Getting Started

Prerequisites

  • Docker (version 20.10+)
  • Docker Compose (version 2.0+)
  • Make (optional, but recommended)

Quick Start with Make

  1. Clone the repository:
git clone <repository-url>
cd ontrack
  1. Make scripts executable (first time only):
chmod +x docker/scripts/*.sh
  1. Start everything with one command:
make go

This 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

Common Make Commands

# 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 commands

Manual Setup (without Make)

If 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 backend

Environment Variables

Environment variables are pre-configured in .env.docker with defaults for local development.

To customize:

cp .env.docker .env
# Edit .env with your configuration

Running Without Docker

If you prefer to run without Docker:

  1. Install dependencies:
cd apps/core
npm install
  1. Set up PostgreSQL and Redis locally

  2. Configure .env file

  3. Run migrations:

npm run migration:run
  1. Start the application:
npm run start:dev

Testing

With Make (recommended)

# 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

Without Make

# 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:cov

Environment Variables

See .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

API documentation will be available via Swagger at /api/docs when the application is running.


Development Guidelines

Clean Architecture Principles

  1. Dependency Rule: Dependencies only point inward. Outer layers depend on inner layers, never vice versa.
  2. Domain Independence: Domain layer has zero external dependencies.
  3. Use Cases: Application layer orchestrates domain logic without implementing business rules.
  4. Interfaces: Use ports/interfaces to invert dependencies.

Domain-Driven Design

  1. Bounded Contexts: Keep contexts isolated with clear boundaries.
  2. Aggregates: Ensure consistency boundaries are respected.
  3. Domain Events: Use events for cross-context communication.
  4. Ubiquitous Language: Use business terminology in code.

About

System for splitting expenses among group members using Clean Architecture + DDD, built with NestJS + TypeScript + AWS. Front in NextJs

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published