This is a full-stack web application for managing financial assets and calculating average interest rates. Built with FastAPI (Python) backend and React (TypeScript) frontend, showcasing clean architecture principles and comprehensive testing strategies.
The backend follows Clean Architecture principles with Domain-Driven Design patterns:
backend/app/
βββ contexts/ # Bounded contexts (Assets, Health, Shared)
β βββ assets/ # Assets domain context
β β βββ domain/ # Core business logic
β β β βββ entities/ # Domain entities (Asset, AssetsList)
β β β βββ repositories/ # Repository interfaces
β β β βββ services/ # Domain services
β β βββ application/ # Use cases/application services
β β βββ infrastructure/ # External concerns (API, persistence)
β β βββ containers/ # Dependency injection
β βββ shared/ # Shared domain concepts
βββ core/ # Application core
β βββ containers/ # Main DI container
β βββ settings/ # Configuration
βββ main.py # Application entry point
The frontend uses a feature-based architecture with clear separation of concerns:
frontend/src/
βββ features/ # Feature modules
β βββ assets/ # Assets feature
β βββ domain/ # Domain entities and repositories
β βββ application/ # Application services
β βββ infrastructure/ # API clients and external integrations
β βββ ui/ # UI components and hooks
βββ pages/ # Page components
βββ router/ # Routing configuration
βββ shared/ # Shared utilities and configurations
- Asset Management: Create and manage financial assets with interest rates
- Average Interest Rate Calculation: Real-time calculation and display of average interest rates
- Dynamic Form Management: Add/remove assets dynamically with validation
- Real-time Updates: Frontend automatically refreshes average interest rate after asset creation
- Comprehensive Validation: Both client-side and server-side validation with detailed error handling
- FastAPI - Modern, fast web framework for building APIs
- Pydantic - Data validation and settings management
- Dependency Injector - Dependency injection container
- Python 3.12+ - Modern Python with type hints
- React 19 - Latest React with modern features
- TypeScript - Type-safe JavaScript
- Material-UI (MUI) - Modern React component library
- React Hook Form - Performant forms with easy validation
- TanStack Query - Powerful data synchronization for React
- Zod - TypeScript-first schema validation
- Vite - Fast build tool and dev server
- Unit Tests: Domain logic and services with
- Integration Tests: API endpoints with real HTTP requests
- E2E Tests: Complete user workflows with Cypress
- Linting: ESLint (frontend) + Ruff & MyPy (backend)
- Formatting: Prettier (frontend) + Ruff (backend)
- Type Checking: TypeScript + MyPy
- Pre-commit Hooks: Automated quality checks
- Python 3.12+
- Node.js 18+
- uv (Python package manager)
cd backend
# Install dependencies
uv sync
# Run the development server
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000The API will be available at http://localhost:8000 with interactive docs at http://localhost:8000/docs.
cd frontend
# Install **dependencies**
npm install
# Start development server
npm run devThe frontend will be available at http://localhost:5173.
Backend Tests:
cd backend
uv run pytestFrontend E2E Tests:
cd frontend
npm run test:e2eWhat I did: I structured the backend following Clean Architecture, splitting it into three main layers: domain, application, and infrastructure. At the first level, you may find the bounded contexts, which are the main areas of the business.
Why:
- It makes the project much easier to understand and maintain, each layer has a clear purpose.
- I can test each part independently, using mocks where needed.
- It gives me flexibility to change implementations later (for example, switching from in-memory storage to a real database).
- Most importantly, it keeps the business logic clean and isolated from technical details.
Trade-offs:
β Great maintainability, testability, and flexibility.
β Slightly more boilerplate and complexity at the start.
What I did: I use the dependency-injector library to manage dependencies throughout the backend app, since I feel very comfortable with the way of work it provides to developers.
Why:
- It makes testing much easier, so I can plug in mocks instead of real services.
- Thereβs a single place to define how everything is wired together.
- It handles lifecycle management cleanly (singletons, factories, etc.).
- It plays nicely with type hints, which improves safety and developer experience.
Trade-offs:
β Strong testability and clear configuration management.
β Requires a bit of setup and some learning to get used to.
What I did: For now, data is stored in memory instead of connecting to a real database.
Why:
- It keeps things simple, without external dependencies or setup like Docker.
- Perfect for demos and quick local testing.
- Thanks to the repository pattern, I can easily replace it later with a real database.
- It speeds up early development.
Trade-offs:
β Very fast setup and iteration.
β Data disappears on restart, so not suitable for production.
What I did: At first level, I organized the frontend by feature, not by technical layers (like components, hooks, utils, etc.). This is because I want to keep the codebase as simple and easy to understand as possible, and the presentational layer in frontend apps takes more importance than in backend, since the UI represents most of the code in the repo. Although, I end up using clean architecture in the deeper layers of the code, because frontend apps also includes business logic, so it can benefit from it. I personally consider this mix of approaches as a good balance, which is more self-explanatory and easier to understand.
Why:
- It scales well, so new features can be added without touching unrelated parts.
- Teams can work in parallel on different features.
- Each feature keeps all its logic and UI together, which is easier to find and modify.
- It leads to a cleaner, more maintainable structure overall.
Trade-offs:
β Great for scalability and team collaboration.
β Can be verbose and complex for small projects.
What I did: I chose React Hook Form for handling form state, combined with Zod for schema validation.
Why:
- React Hook Form uses uncontrolled inputs, so it avoids unnecessary re-renders and improves performance.
- Zod gives us runtime validation and generates TypeScript types automatically. Which really helps to localize errors quickly without spreading them across the codebase.
- The developer experience is excellent, with built-in error handling and good validation feedback.
- Both libraries are lightweight compared to other alternatives.
Trade-offs:
β Great performance and DX.
β Takes some time to master for advanced form use cases.
What I did: I use TanStack Query (React Query) for handling server state and API requests.
Why:
- It automatically caches requests and refreshes data in the background.
- Built-in handling for loading and error states simplifies our code.
- Supports optimistic updates out of the box, giving users instant feedback.
- It also comes with excellent DevTools and debugging capabilities.
Trade-offs:
β Fantastic caching, error management, and DX. Once known the basics, it's a breeze to use.
β Slightly increases bundle size and has its own learning curve.
What I did: I use Material-UI for styling the frontend app.
Why:
- It's a modern and popular component library that integrates well with React.
- It's a great way to keep the codebase consistent and easy to maintain.
- It's the library which I personally have more experience with, so it's easier to use for me. Trade-offs:
β Huge library with a lot of components and features, which makes it easier to build complex UIs. Also a great DX, with a lot of built-in features and utilities.
β Slightly increases bundle size and has its own learning curve.
What I did: I use precommit hooks to enforce code quality and consistency throughout the codebase.
Why:
- It helps to catch errors and bugs early, before they reach the production.
- It helps to keep the codebase consistent and easy to maintain.
- It enforces best practices and coding standards.
- It helps collaborators to focus on the features and business logic, instead of worrying about code quality.
Trade-offs:
β Great for code quality and consistency.
β Big effort to setup, since it requires some time to learn the basics and configure the rules.
- Replace
InMemoryAssetsListRepositorywithPostgreSQLAssetsListRepository - Implement ORM like SQLAlchemy for database operations
- Add database migrations with Alembic
- Implement connection pooling (async is possible, but not necessary for now)
- Add database health checks
- Implement JWT-based authentication
- Add role-based access control (RBAC)
- Secure API endpoints with middleware
- Add user management features
- Implement API versioning
- Add logging from request ID
- Implement metrics collection
- Error tracking
- Add rate limiting
- SQL injection prevention
- CORS configuration for production domains
- HTTPS enforcement
- Security headers middleware
- Containerization: Docker containers for both frontend and backend
- CI/CD: GitHub Actions or CircleCI for automated testing, ensure code quality and deployment
- Infrastructure: Cloud-native approach (AWS/GCP/Azure)
- Database query optimization
- Redis caching layer
- CDN for static assets
- API response compression
- Frontend code splitting and lazy loading
This project is licensed under the MIT License - see the LICENSE file for details.