A robust expense management and debt simplification system
Split expenses fairly, track balances, and settle debts with ease
- Overview
- Key Features
- Architecture Highlights
- Tech Stack
- Getting Started
- API Features
- Domain Model
- Development
- Testing
- Documentation
- Project Structure
- License
FairShare is a production-ready expense management API built with enterprise-grade architecture patterns. Inspired by apps like Splitwise, it provides a robust backend for tracking shared expenses, managing group finances, and simplifying complex debt scenarios.
- Financial Correctness First: Built as a financial ledger system, not just a CRUD app
- Mobile-First Design: Idempotency keys protect against duplicate operations from network retries
- Intelligent Debt Simplification: Automatically reduces the number of transactions needed to settle all debts
- Enterprise Architecture: Clean domain-driven design with proper separation of concerns
- Production Ready: Comprehensive error handling, validation, and transaction management
- Multiple Split Types: Equal, exact amount, and percentage-based splits
- Multi-Currency Support: Handle expenses in different currencies
- Flexible Participants: Add any group member to an expense
- Real-time Balance Updates: Automatic balance recalculation after each expense
- Group Management: Create and manage expense groups
- Role-Based Access: Admin and member roles with appropriate permissions
- Multi-User Support: Multiple users can manage expenses in shared groups
- Audit Trail: Complete history of all expenses and settlements
- Smart Algorithm: Reduces the number of required transactions
- Balance Snapshots: Efficiently tracks who owes whom
- Settlement Tracking: Record and track debt repayments
- Historical Accuracy: Maintain complete financial records
- JWT Authentication: Secure user authentication
- Idempotency Keys: Prevent duplicate operations (24-hour window)
- Transaction Safety: ACID-compliant database operations
- Validation: Comprehensive input validation with class-validator
- Error Handling: Graceful error handling with custom filters
FairShare follows Domain-Driven Design (DDD) principles with a clear separation between:
- Write Model: Expenses and settlements (source of truth)
- Read Model: Balance snapshots (derived state)
- Strategy Pattern: Pluggable split calculation strategies (Equal, Exact, Percentage)
- Domain Services: Pure business logic separated from infrastructure
- Repository Pattern: Abstracted data access via TypeORM
- Interceptor Pattern: Idempotency handling via NestJS interceptors
- Guard Pattern: Authentication and authorization
The system enforces strict domain rules:
- Balance snapshots are always consistent with expenses
- All financial calculations are precise (no floating-point errors)
- Users cannot create expenses in groups they don't belong to
- Expense participants must sum to the total amount
- No self-debt scenarios
- NestJS - Progressive Node.js framework
- TypeScript - Type-safe JavaScript
- PostgreSQL - Primary relational database
- TypeORM - Object-relational mapping
- Migrations - Version-controlled schema changes
- Passport - Authentication middleware
- JWT - JSON Web Token strategy
- Local Strategy - Username/password authentication
- class-validator - Decorator-based validation
- class-transformer - Object transformation
- ESLint - Linting
- Prettier - Code formatting
- Commitizen - Conventional commits
- Node.js (v18 or higher)
- PostgreSQL (v14 or higher)
- Redis (v6 or higher)
- npm or yarn
-
Clone the repository
git clone https://github.com/naw7az/fairshare-api.git cd fairshare-api -
Install dependencies
npm install
-
Configure environment variables
Create a
.envfile in the root directory:# Database DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres DB_PASSWORD=your_password DB_NAME=fairshare # Redis REDIS_HOST=localhost REDIS_PORT=6379 # JWT JWT_SECRET=your_jwt_secret_key JWT_EXPIRATION=7d # Application PORT=3000 NODE_ENV=development
-
Run database migrations
npm run migration:run
-
Start the development server
npm run watch
The API will be available at http://localhost:3000
POST /auth/register- Register a new userPOST /auth/login- Login and receive JWT token
GET /users/me- Get current user profilePATCH /users/me- Update user profile
POST /groups- Create a new groupGET /groups- List user's groupsGET /groups/:id- Get group detailsPATCH /groups/:id- Update groupDELETE /groups/:id- Delete group
POST /groups/:id/members- Add member to groupGET /groups/:id/members- List group membersDELETE /groups/:id/members/:userId- Remove member
POST /groups/:id/expenses- Create expense (requires idempotency key)GET /groups/:id/expenses- List group expensesGET /groups/:id/expenses/:expenseId- Get expense detailsPATCH /groups/:id/expenses/:expenseId- Update expense (requires idempotency key)DELETE /groups/:id/expenses/:expenseId- Delete expense
GET /groups/:id/balances- Get simplified balances for groupGET /groups/:id/balances/user/:userId- Get user's balances in group
POST /groups/:id/settlements- Record a settlement (requires idempotency key)GET /groups/:id/settlements- List settlementsPATCH /groups/:id/settlements/:settlementId- Update settlement (requires idempotency key)
User ─────┐
│
├──→ GroupMember ──→ Group
│ │
│ │
└──→ Expense ──────────┘
│
├──→ ExpenseParticipant
│
└──→ BalanceSnapshot (derived)
Settlement ────→ Group
- User: System users with authentication credentials
- Group: Container for shared expenses
- GroupMember: Association between users and groups (with roles)
- Expense: Record of money spent with split information
- ExpenseParticipant: Individual user's share in an expense
- BalanceSnapshot: Derived state showing who owes whom
- Settlement: Record of debt repayment
- IdempotencyKey: Prevents duplicate operations
# Development
npm run watch # Start with hot-reload
npm run start:debug # Start with debugging
# Build
npm run build # Compile TypeScript to JavaScript
npm run start:prod # Run production build
# Code Quality
npm run format # Format code with Prettier
npm run lint # Lint and fix code with ESLint
npm run commit # Commit with Commitizen
# Database
npm run migration:generate # Generate new migration
npm run migration:run # Run pending migrations
npm run migration:revert # Revert last migration
# TypeORM CLI
npm run typeorm <command> # Run TypeORM commands# Unit tests
npm run test
# Unit tests in watch mode
npm run test:watch
# E2E tests
npm run test:e2e
# E2E tests in watch mode
npm run test:e2e:watch
# Test coverage
npm run test:cov- Unit Tests: Located alongside source files (
*.spec.ts) - E2E Tests: Located in
/testdirectory - Test Setup: Automated test database setup with transactions
Comprehensive documentation is available in the /docs directory:
- LLDOverview.md - Low-level design and architecture
- DomainInvariants.md - Business rules and constraints
- Idempotency.md - Idempotency key implementation
- ConcurrencyModel.md - Handling concurrent operations
- Learning.md - Key learnings and decisions
fairshare-api/
├── src/
│ ├── main.ts # Application entry point
│ ├── app.module.ts # Root module
│ ├── data-source.ts # TypeORM configuration
│ │
│ ├── auth/ # Authentication module
│ │ ├── strategies/ # Passport strategies
│ │ ├── guards/ # Auth guards
│ │ └── dto/ # Auth DTOs
│ │
│ ├── users/ # User management
│ ├── groups/ # Group management
│ ├── group-members/ # Group membership
│ ├── expenses/ # Expense management
│ │ └── split-strategy/ # Split calculation strategies
│ ├── expense-participants/ # Expense participant management
│ ├── balance-snapshots/ # Balance calculation
│ ├── settlements/ # Settlement tracking
│ ├── debt-simplifier/ # Debt simplification algorithm
│ ├── idempotency/ # Idempotency key handling
│ ├── redis/ # Redis configuration
│ │
│ └── common/ # Shared utilities
│ ├── decorators/ # Custom decorators
│ ├── dto/ # Common DTOs
│ ├── entities/ # Base entities
│ ├── filters/ # Exception filters
│ ├── types/ # TypeScript types
│ └── validators/ # Custom validators
│
├── migrations/ # Database migrations
├── test/ # E2E tests
├── docs/ # Documentation
└── dist/ # Compiled output
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes using Commitizen (
npm run commit) - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project uses Conventional Commits. Use npm run commit to create properly formatted commits.
This project is licensed under the UNLICENSED license - see the LICENSE file for details.
- Built with NestJS - A progressive Node.js framework
- Inspired by Splitwise - The leading expense splitting app
- Architecture patterns from Domain-Driven Design principles
For questions, issues, or contributions:
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ by the FairShare Team
Star this repo if you find it helpful!