Skip to content

SRH550/fenmo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Expense Tracker - Production Quality Application

A full-stack expense tracking application built with Node.js + Express backend, React frontend with TypeScript, and SQLite database using Prisma ORM.

🎯 Features

Backend

  • βœ… RESTful API with Express.js
  • βœ… SQLite database with Prisma ORM
  • βœ… Idempotency support for safe duplicate request handling
  • βœ… Input validation and error handling
  • βœ… Clean architecture (routes β†’ controllers β†’ services β†’ repository)
  • βœ… Money handling using paise (integers) to avoid floating-point errors
  • βœ… CORS support for development

Frontend

  • βœ… React 18 with TypeScript
  • βœ… Vite bundler for fast development
  • βœ… Add expenses with category, amount, description, date
  • βœ… Filter expenses by category
  • βœ… Sort by date (newest/oldest first)
  • βœ… Real-time total calculation
  • βœ… Loading and error states
  • βœ… Responsive design
  • βœ… Idempotency-key header support

πŸ“‹ Architecture

Backend Structure

backend/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.ts              # Express app setup, Prisma client
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”œβ”€β”€ errorHandler.ts   # Error handling & logging
β”‚   β”‚   └── idempotency.ts    # Idempotency middleware
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   └── expenses.ts       # Route definitions
β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   └── expenseController.ts  # Request handlers
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   └── expenseService.ts     # Business logic
β”‚   β”œβ”€β”€ repository/
β”‚   β”‚   └── expenseRepository.ts  # Database access
β”‚   └── utils/
β”‚       └── validation.ts     # Input validation & money conversion
β”œβ”€β”€ prisma/
β”‚   └── schema.prisma         # Database schema
β”œβ”€β”€ package.json
└── tsconfig.json

Frontend Structure

frontend/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ ExpenseForm.tsx       # Add expense form
β”‚   β”‚   β”œβ”€β”€ ExpenseList.tsx       # Display expenses table
β”‚   β”‚   └── ExpenseControls.tsx   # Filter & sort controls
β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   └── useExpenses.ts        # API calls & state management
β”‚   β”œβ”€β”€ types/
β”‚   β”‚   └── index.ts              # TypeScript interfaces
β”‚   β”œβ”€β”€ styles/
β”‚   β”‚   β”œβ”€β”€ App.css
β”‚   β”‚   β”œβ”€β”€ ExpenseForm.css
β”‚   β”‚   β”œβ”€β”€ ExpenseList.css
β”‚   β”‚   β”œβ”€β”€ ExpenseControls.css
β”‚   β”‚   └── index.css
β”‚   β”œβ”€β”€ App.tsx                   # Main app component
β”‚   β”œβ”€β”€ main.tsx                  # Entry point
β”‚   └── App.css
β”œβ”€β”€ vite.config.ts
β”œβ”€β”€ index.html
β”œβ”€β”€ package.json
└── tsconfig.json

πŸ—οΈ Design Decisions

1. Money Handling (Paise-based Integers)

  • Decision: Store amounts as integers in paise (β‚Ή1 = 100 paise)
  • Why: Avoids floating-point precision errors common in financial applications
  • Trade-off: API and frontend work with both paise (storage) and rupees (display)
  • Implementation: Conversion utilities in validation.ts

2. Idempotency for Duplicate Requests

  • Decision: Implemented idempotency using Idempotency-Key header
  • How:
    • Store request hash and response in IdempotencyKey table
    • On retry with same key, return cached response
    • 24-hour expiration for cleanup
  • Trade-off: Adds database overhead but ensures data integrity
  • Use Case: Handles network retries, duplicate form submissions

3. Clean Layered Architecture

  • Decision: Separate concerns into routes β†’ controllers β†’ services β†’ repository
  • Why:
    • Testability: Each layer can be tested independently
    • Maintainability: Easy to modify business logic without touching routes
    • Reusability: Services can be used across multiple routes
  • Trade-off: More files for small CRUD operations, but scales well

4. Custom Hooks for API Calls

  • Decision: useExpenses, useCreateExpense, useExpenseSummary hooks
  • Why:
    • Reusable API logic
    • Centralized error handling
    • Consistent loading states
  • Trade-off: Could use React Query, but custom hooks are lightweight and sufficient

5. SQLite with Prisma

  • Decision: SQLite for local development, Prisma as ORM
  • Why:
    • SQLite: No setup required, file-based, perfect for prototyping
    • Prisma: Type-safe, migrations built-in, excellent DX
  • Trade-off: SQLite not suitable for multi-server deployment (use PostgreSQL in production)

6. CORS Configuration

  • Decision: Allow http://localhost:5173 (Vite dev server)
  • Why: Needed for local development
  • Trade-off: Must be changed for production (use environment-based configuration)

πŸ”„ API Endpoints

POST /expenses

Create a new expense (idempotent)

Headers:

Content-Type: application/json
Idempotency-Key: <uuid>  # Required for idempotency

Request Body:

{
  "amount": 50000,          // in paise (β‚Ή500)
  "category": "Food",
  "description": "Lunch",
  "date": "2024-04-28T12:00:00Z"
}

Response (201):

{
  "success": true,
  "data": {
    "id": "uuid",
    "amount": 500,            // in rupees
    "amountPaise": 50000,     // in paise
    "category": "Food",
    "description": "Lunch",
    "date": "2024-04-28T12:00:00Z",
    "createdAt": "2024-04-28T12:00:00Z"
  }
}

GET /expenses

Get expenses with filtering and sorting

Query Parameters:

GET /expenses?category=Food&sort=date_desc

Response (200):

{
  "success": true,
  "data": [...],
  "summary": {
    "count": 5,
    "total": 2500
  }
}

GET /expenses/summary

Get expense summary by category

Response (200):

{
  "success": true,
  "data": [
    {
      "category": "Food",
      "total": 1500,
      "totalPaise": 150000,
      "count": 3
    }
  ]
}

⚠️ Validation & Error Handling

Backend Validation

  • βœ… Amount must be > 0
  • βœ… Category is required (non-empty string)
  • βœ… Date must be valid ISO string
  • βœ… Description optional but must be string if provided

Response Format

{
  "error": "Validation failed",
  "details": [
    { "field": "amount", "message": "Amount must be greater than 0" }
  ]
}

πŸš€ Getting Started

Prerequisites

  • Node.js 16+ (LTS recommended)
  • npm or yarn

Backend Setup

  1. Navigate to backend directory:

    cd backend
  2. Install dependencies:

    npm install
  3. Setup Prisma database:

    npm run prisma:generate
    npm run prisma:migrate
  4. Start development server:

    npm run dev

    Server runs on http://localhost:3000

Frontend Setup

  1. Navigate to frontend directory:

    cd ../frontend
  2. Install dependencies:

    npm install
  3. Start development server:

    npm run dev

    Frontend runs on https://fenmo-blue.vercel.app

Using the Application

  1. Open https://fenmo-blue.vercel.app in browser
  2. Fill the expense form:
    • Amount (in rupees)
    • Category (Food, Transport, etc.)
    • Description (optional)
    • Date
  3. Click "Add Expense"
  4. View expenses in table
  5. Filter by category or sort by date

πŸ“Š Database Schema

model Expense {
  id          String   @id @default(uuid())
  amount      Int      // in paise
  category    String   @db.Text
  description String   @db.Text
  date        DateTime
  createdAt   DateTime @default(now())
}

model IdempotencyKey {
  id          String   @id @default(uuid())
  key         String   @unique
  requestHash String   @db.Text
  response    String   @db.Text
  createdAt   DateTime @default(now())
  expiresAt   DateTime
}

πŸ” Trade-offs & Considerations

1. Idempotency Cache Expiry

  • Current: 24 hours
  • Trade-off: Could be shorter for high-traffic apps, but 24h safe for most use cases
  • Future: Implement background job for cleanup

2. Error Handling

  • Current: Generic error messages for security
  • Trade-off: Less helpful for debugging in production
  • Solution: Enable detailed logging only in development

3. Frontend State Management

  • Current: React hooks + custom API hooks
  • Trade-off: Simpler than Redux/Zustand, but less structured for large apps
  • Future: Consider Redux if app scales significantly

4. Category List

  • Current: Fetched dynamically from expenses
  • Trade-off: No separate category table
  • Future: Add predefined categories in database for consistency

Money Handling Philosophy

This app treats money as integers (paise) to avoid floating-point arithmetic issues:

  • β‚Ή500 = 50,000 paise
  • β‚Ή1.50 = 150 paise

This is a best practice in financial systems and eliminates rounding errors.

Idempotency Implementation

The Idempotency-Key header allows clients to safely retry requests:

  • Same key + same request = cached response (no duplicate expense)
  • Different key = new expense created
  • Expires after 24 hours for database cleanup

Clean Architecture Benefits

  • Routes: Define endpoints and HTTP methods
  • Controllers: Handle request/response, call services
  • Services: Contain business logic, format responses
  • Repository: Database operations, single source of truth

This structure makes the app:

  • Easy to test (mock each layer independently)
  • Easy to extend (add new features without touching existing code)
  • Easy to understand (clear separation of concerns)

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors