prathamesh424/Assignment-Smart-Expense-Tracker-API

★ 1Forks 0PythonGitHub ↗Compare

README

Smart Expense Tracker API

A REST API to manage personal expenses, built with Python + FastAPI.

Expenses are stored in a local JSON file and persist across server restarts — no database setup required. The API includes interactive OpenAPI (Swagger UI) documentation at /docs as the optional bonus feature.

Requirements

  • Python 3.11+

Installation

pip install -r requirements.txt

Run the Server

uvicorn src.main:app --reload

Run the Tests

pytest -v

Quick Start

# 1. Clone and enter the project
git clone https://github.com/prathamesh424/Assignment-Smart-Expense-Tracker-API.git
cd Assignment-Smart-Expense-Tracker-API

# 2. Create a virtual environment (recommended)
python -m venv .venv
.venv\Scripts\activate        # Windows
# source .venv/bin/activate   # macOS / Linux

# 3. Install dependencies
pip install -r requirements.txt

# 4. Start the server
uvicorn src.main:app --reload

# 5. Run the test suite
pytest -v

The server starts at http://127.0.0.1:8000.
Interactive Swagger docs are at http://127.0.0.1:8000/docs.


API Endpoints

Method Endpoint Description Status Codes
GET / Health check 200
POST /expenses Add a new expense 201, 422
GET /expenses List all expenses 200
GET /expenses?category=Food Filter by category 200
GET /expenses/total Total of all expenses 200
GET /expenses/total?category=Food Total for a specific category 200
GET /expenses/{id} Get a single expense by ID 200, 404
DELETE /expenses/{id} Delete an expense 204, 404

Usage Examples

Add an Expense

curl -X POST http://127.0.0.1:8000/expenses \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Grocery shopping",
    "amount": 45.99,
    "category": "Food",
    "date": "2025-07-30"
  }'
{
  "id": "d3b07384-d113-4ec2-8a1e-f2c9b6c3e7a1",
  "title": "Grocery shopping",
  "amount": 45.99,
  "category": "Food",
  "date": "2025-07-30"
}

List All Expenses

curl http://127.0.0.1:8000/expenses

Filter by Category

curl http://127.0.0.1:8000/expenses?category=Food

Category filtering is case-insensitive — food, Food, and FOOD all match.

Get Total Expenses

# Overall total
curl http://127.0.0.1:8000/expenses/total

# Total for a specific category
curl http://127.0.0.1:8000/expenses/total?category=Food
{
  "total": 45.99,
  "category": "Food",
  "count": 1
}

Delete an Expense

curl -X DELETE http://127.0.0.1:8000/expenses/{id}

Returns 204 No Content on success, 404 if the expense doesn't exist.


Project Structure

├── README.md            # This file
├── AI_NOTES.md          # AI usage documentation
├── requirements.txt     # Pinned Python dependencies
├── src/
│   ├── __init__.py
│   ├── main.py          # FastAPI app initialisation and root endpoint
│   ├── models.py        # Pydantic schemas for validation and serialization
│   ├── routes.py        # All /expenses endpoint definitions
│   └── storage.py       # JSON file persistence layer
└── tests/
    ├── __init__.py
    ├── conftest.py      # Shared pytest fixtures (isolated storage per test)
    └── test_expenses.py # 22 test cases covering all endpoints

Input Validation

All request bodies are validated via Pydantic before reaching the endpoint logic:

Field Rules
title Required, 1–200 chars, cannot be blank/whitespace
amount Required, must be a positive number (> 0)
category Required, 1–100 chars, cannot be blank/whitespace
date Required, must be a valid date in YYYY-MM-DD

Invalid requests return 422 Unprocessable Entity with a detailed error body showing exactly which field failed and why.


Testing

pytest -v

The test suite contains 22 tests organised into 6 groups:

Group Tests What's Covered
TestCreateExpense 8 Valid creation, UUID generation, validation rejections
TestListExpenses 5 Empty list, populated list, category filter, case-insensitivity
TestGetExpenseById 2 Found (200), not found (404)
TestDeleteExpense 2 Successful deletion (204), not found (404)
TestTotalExpenses 4 Empty total, overall sum, category sum, missing category
TestRoot 1 Health check response

Each test runs against an isolated temporary JSON file (via pytest tmp_path), so tests never share state or interfere with each other.


Design Decisions

Decision Rationale
FastAPI Built-in request validation (Pydantic), automatic OpenAPI docs, and minimal boilerplate
UUID identifiers Simple to generate, unique without maintaining a global counter, and work well with JSON storage
JSON file storage Human-readable, zero setup, satisfies the "no database" constraint, persists across server restarts
Case-insensitive filtering ?category=food matches "Food" — better UX without requiring users to know exact casing
Injectable storage Routes accept a storage instance via init_router(), making tests fully isolated with no shared state
Synchronous endpoints File I/O is inherently blocking; async would add complexity without performance benefit

Optional Bonus: OpenAPI (Swagger UI)

FastAPI auto-generates an interactive Swagger UI from the route definitions and Pydantic models. Available at /docs when the server is running. Every endpoint can be tested directly from the browser — no external tools needed.

Contributors

prathamesh424

Issues