Add backend API for personal finance management application
- Introduced a comprehensive backend API using TypeScript, Fastify, and PostgreSQL. - Added essential files including architecture documentation, environment configuration, and Docker setup. - Implemented RESTful routes for managing assets, liabilities, clients, invoices, and cashflow. - Established a robust database schema with Prisma for data management. - Integrated middleware for authentication and error handling. - Created service and repository layers to adhere to SOLID principles and clean architecture. - Included example environment variables for development, staging, and production setups.
This commit is contained in:
366
backend-api/ARCHITECTURE.md
Normal file
366
backend-api/ARCHITECTURE.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# Personal Finances API - Architecture Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
This backend API is built following **SOLID principles** and **clean architecture** patterns using TypeScript, Fastify, and PostgreSQL.
|
||||
|
||||
## SOLID Principles Implementation
|
||||
|
||||
### 1. Single Responsibility Principle (SRP)
|
||||
Each class has one well-defined responsibility:
|
||||
|
||||
- **Controllers** - Handle HTTP requests/responses only
|
||||
- **Services** - Contain business logic only
|
||||
- **Repositories** - Handle data access only
|
||||
- **Middleware** - Handle cross-cutting concerns (auth, errors)
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
// AssetService - ONLY handles asset business logic
|
||||
export class AssetService {
|
||||
async create(userId: string, data: CreateAssetDTO): Promise<Asset>
|
||||
async update(id: string, userId: string, data: UpdateAssetDTO): Promise<Asset>
|
||||
// ...
|
||||
}
|
||||
|
||||
// AssetRepository - ONLY handles database operations
|
||||
export class AssetRepository {
|
||||
async findById(id: string): Promise<Asset | null>
|
||||
async create(data: Prisma.AssetCreateInput): Promise<Asset>
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Open/Closed Principle (OCP)
|
||||
The system is open for extension but closed for modification:
|
||||
|
||||
- **Custom Error Classes** - Extend `AppError` base class for new error types
|
||||
- **Repository Interfaces** - Implement `IRepository<T>` for new entities
|
||||
- **Service Pattern** - Add new services without modifying existing ones
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
// Extensible error hierarchy
|
||||
export abstract class AppError extends Error {
|
||||
abstract statusCode: number;
|
||||
}
|
||||
|
||||
export class NotFoundError extends AppError {
|
||||
statusCode = 404;
|
||||
}
|
||||
|
||||
export class ValidationError extends AppError {
|
||||
statusCode = 400;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Liskov Substitution Principle (LSP)
|
||||
Derived classes can substitute their base classes:
|
||||
|
||||
```typescript
|
||||
// Base interface
|
||||
export interface IRepository<T> {
|
||||
findById(id: string): Promise<T | null>;
|
||||
create(data: Partial<T>): Promise<T>;
|
||||
}
|
||||
|
||||
// Specialized interface extends base without breaking it
|
||||
export interface IUserScopedRepository<T> extends Omit<IRepository<T>, 'findAll'> {
|
||||
findAllByUser(userId: string): Promise<T[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Interface Segregation Principle (ISP)
|
||||
Clients depend only on interfaces they use:
|
||||
|
||||
- `IRepository<T>` - Base CRUD operations
|
||||
- `IUserScopedRepository<T>` - User-scoped operations
|
||||
- Specific methods in services (e.g., `getTotalValue()` in AssetService)
|
||||
|
||||
### 5. Dependency Inversion Principle (DIP)
|
||||
High-level modules depend on abstractions:
|
||||
|
||||
```typescript
|
||||
// Service depends on repository abstraction, not concrete implementation
|
||||
export class AuthService {
|
||||
constructor(private userRepository: UserRepository) {}
|
||||
// UserRepository implements IRepository<User>
|
||||
}
|
||||
|
||||
// Singleton database connection
|
||||
class DatabaseConnection {
|
||||
private static instance: PrismaClient;
|
||||
public static getInstance(): PrismaClient {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
### 1. Presentation Layer (Controllers & Routes)
|
||||
- **Location**: `src/controllers/`, `src/routes/`
|
||||
- **Purpose**: Handle HTTP requests/responses
|
||||
- **Responsibilities**:
|
||||
- Parse request parameters
|
||||
- Validate input schemas (using Zod)
|
||||
- Call service layer
|
||||
- Format responses
|
||||
|
||||
```typescript
|
||||
export class AssetController {
|
||||
async create(request: FastifyRequest, reply: FastifyReply) {
|
||||
const userId = getUserId(request);
|
||||
const data = createAssetSchema.parse(request.body); // Validation
|
||||
const asset = await this.assetService.create(userId, data); // Business logic
|
||||
return reply.status(201).send({asset}); // Response
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Business Logic Layer (Services)
|
||||
- **Location**: `src/services/`
|
||||
- **Purpose**: Implement business rules
|
||||
- **Responsibilities**:
|
||||
- Validate business rules
|
||||
- Coordinate between repositories
|
||||
- Perform calculations
|
||||
- Enforce authorization
|
||||
|
||||
```typescript
|
||||
export class InvoiceService {
|
||||
async create(userId: string, data: CreateInvoiceDTO): Promise<Invoice> {
|
||||
this.validateInvoiceData(data); // Business rule
|
||||
const invoiceNumber = await this.generateInvoiceNumber(userId); // Logic
|
||||
const subtotal = this.calculateSubtotal(data.lineItems); // Calculation
|
||||
return this.invoiceRepository.create({...}); // Data access
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Data Access Layer (Repositories)
|
||||
- **Location**: `src/repositories/`
|
||||
- **Purpose**: Abstract database operations
|
||||
- **Responsibilities**:
|
||||
- CRUD operations
|
||||
- Query composition
|
||||
- Data mapping
|
||||
|
||||
```typescript
|
||||
export class AssetRepository implements IUserScopedRepository<Asset> {
|
||||
async findAllByUser(userId: string): Promise<Asset[]> {
|
||||
return prisma.asset.findMany({
|
||||
where: {userId},
|
||||
orderBy: {createdAt: 'desc'},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Cross-Cutting Concerns (Middleware & Utils)
|
||||
- **Location**: `src/middleware/`, `src/utils/`
|
||||
- **Purpose**: Handle common functionality
|
||||
- **Components**:
|
||||
- Authentication middleware
|
||||
- Error handling
|
||||
- Password utilities
|
||||
- Custom errors
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
HTTP Request
|
||||
↓
|
||||
Route (Fastify)
|
||||
↓
|
||||
Controller
|
||||
├→ Validate Input (Zod)
|
||||
├→ Extract User ID (Middleware)
|
||||
└→ Call Service
|
||||
↓
|
||||
Service
|
||||
├→ Validate Business Rules
|
||||
├→ Perform Calculations
|
||||
└→ Call Repository
|
||||
↓
|
||||
Repository
|
||||
├→ Compose Query
|
||||
└→ Execute via Prisma
|
||||
↓
|
||||
Database (PostgreSQL)
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Entity Relationships
|
||||
|
||||
```
|
||||
User
|
||||
├── Assets (1:N)
|
||||
├── Liabilities (1:N)
|
||||
├── NetWorthSnapshots (1:N)
|
||||
├── Clients (1:N)
|
||||
│ └── Invoices (1:N)
|
||||
│ └── InvoiceLineItems (1:N)
|
||||
├── IncomeSources (1:N)
|
||||
├── Expenses (1:N)
|
||||
├── Transactions (1:N)
|
||||
└── DebtCategories (1:N)
|
||||
└── DebtAccounts (1:N)
|
||||
└── DebtPayments (1:N)
|
||||
```
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **Cascade Deletes**: User deletion cascades to all related data
|
||||
2. **UUID Primary Keys**: For security and distributed systems
|
||||
3. **Timestamps**: All entities track creation/update times
|
||||
4. **Enums**: Type-safe status and category fields
|
||||
5. **Composite Indexes**: Optimized queries on user_id + other fields
|
||||
|
||||
## Security Features
|
||||
|
||||
### 1. Authentication
|
||||
- JWT tokens with configurable expiration
|
||||
- Secure password hashing (bcrypt with 10 rounds)
|
||||
- Password complexity requirements
|
||||
|
||||
### 2. Authorization
|
||||
- User-scoped data access
|
||||
- Repository methods verify ownership
|
||||
- Middleware extracts authenticated user
|
||||
|
||||
### 3. Input Validation
|
||||
- Zod schemas for runtime validation
|
||||
- Type-safe request/response handling
|
||||
- SQL injection prevention (Prisma ORM)
|
||||
|
||||
### 4. Error Handling
|
||||
- Custom error classes
|
||||
- No sensitive information in error messages
|
||||
- Proper HTTP status codes
|
||||
|
||||
## API Design
|
||||
|
||||
### RESTful Conventions
|
||||
- `GET /api/resources` - List all
|
||||
- `GET /api/resources/:id` - Get one
|
||||
- `POST /api/resources` - Create
|
||||
- `PUT /api/resources/:id` - Update (full)
|
||||
- `PATCH /api/resources/:id` - Update (partial)
|
||||
- `DELETE /api/resources/:id` - Delete
|
||||
|
||||
### Response Format
|
||||
```json
|
||||
{
|
||||
"resource": { /* data */ },
|
||||
// or
|
||||
"resources": [ /* array */ ],
|
||||
// or on error
|
||||
"error": "ErrorType",
|
||||
"message": "Human-readable message"
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Status Codes
|
||||
- `200 OK` - Successful GET/PUT/PATCH
|
||||
- `201 Created` - Successful POST
|
||||
- `204 No Content` - Successful DELETE
|
||||
- `400 Bad Request` - Validation error
|
||||
- `401 Unauthorized` - Authentication required
|
||||
- `403 Forbidden` - Insufficient permissions
|
||||
- `404 Not Found` - Resource not found
|
||||
- `409 Conflict` - Duplicate resource
|
||||
- `500 Internal Server Error` - Server error
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Test services in isolation
|
||||
- Mock repository dependencies
|
||||
- Test business logic thoroughly
|
||||
|
||||
### Integration Tests
|
||||
- Test API endpoints
|
||||
- Use test database
|
||||
- Verify request/response flow
|
||||
|
||||
### E2E Tests
|
||||
- Test complete user flows
|
||||
- Verify authentication
|
||||
- Test error scenarios
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Database
|
||||
- Indexes on frequently queried fields
|
||||
- Connection pooling (Prisma)
|
||||
- Efficient query composition
|
||||
|
||||
### Caching
|
||||
- JWT tokens cached in client
|
||||
- Consider Redis for session management
|
||||
- Database query result caching
|
||||
|
||||
### Scalability
|
||||
- Stateless API (horizontal scaling)
|
||||
- Database migrations for schema changes
|
||||
- Environment-based configuration
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Add New Feature**:
|
||||
- Define Prisma schema
|
||||
- Run migration
|
||||
- Create repository interface
|
||||
- Implement repository
|
||||
- Create service with business logic
|
||||
- Add controller
|
||||
- Define routes
|
||||
- Add tests
|
||||
|
||||
2. **Modify Existing Feature**:
|
||||
- Update service layer (business logic)
|
||||
- Update repository if needed
|
||||
- Update schema if needed
|
||||
- Update tests
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Code Organization
|
||||
✅ One class per file
|
||||
✅ Group related files in directories
|
||||
✅ Use barrel exports (index.ts)
|
||||
✅ Consistent naming conventions
|
||||
|
||||
### Error Handling
|
||||
✅ Use custom error classes
|
||||
✅ Validate at boundaries
|
||||
✅ Log errors appropriately
|
||||
✅ Return user-friendly messages
|
||||
|
||||
### Security
|
||||
✅ Never log sensitive data
|
||||
✅ Validate all inputs
|
||||
✅ Use parameterized queries (Prisma)
|
||||
✅ Implement rate limiting
|
||||
✅ Keep dependencies updated
|
||||
|
||||
### Documentation
|
||||
✅ JSDoc comments for public APIs
|
||||
✅ README for setup instructions
|
||||
✅ API documentation (Swagger)
|
||||
✅ Architecture documentation (this file)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Rate limiting middleware
|
||||
- [ ] Request logging
|
||||
- [ ] Metrics and monitoring
|
||||
- [ ] Database query optimization
|
||||
- [ ] Caching layer
|
||||
- [ ] WebSocket support for real-time updates
|
||||
- [ ] Background job processing
|
||||
- [ ] Email notifications
|
||||
- [ ] Data export functionality
|
||||
- [ ] Multi-tenancy support
|
||||
Reference in New Issue
Block a user