Mini Project Python: Build CLI Expense Tracker dari Nol

Python By TryzTech Team
PythonCLIMini ProjectBeginnerTutorial
Bagikan

Di lima artikel sebelumnya, kamu sudah belajar fondasi Python: variabel, control flow, function, struktur data, file, error handling, module, OOP, project structure, virtual environment, dan testing.

Sekarang waktunya mengikat semuanya menjadi project kecil yang bisa dipakai: CLI expense tracker. Aplikasi ini berjalan di terminal, bisa menambah expense, melihat daftar expense, dan menghitung total.

Target project

Fitur minimal yang akan kita buat:

  • Add expense.
  • List expenses.
  • Show total.
  • Exit.
  • Simpan data ke file JSON.
  • Test untuk logic total.

Contoh flow:

1. Add expense
2. List expenses
3. Show total
4. Exit
Choose: 1
Title: Coffee
Amount: 25000
Saved.

Project ini sengaja kecil. Tujuannya membuat semua konsep fundamental terasa nyata.

Bangun project ini bertahap. Pastikan versi pertama bisa menampilkan menu dan keluar dengan bersih, lalu tambahkan model data, penyimpanan JSON, dan test. Urutan ini membuat setiap error lebih mudah dilacak karena kamu selalu tahu fitur terakhir yang baru ditambahkan.

Struktur folder

Gunakan struktur berikut:

expense-tracker/
  src/
    expense_tracker/
      __init__.py
      app.py
      models.py
      storage.py
      main.py
  tests/
    test_app.py
  requirements.txt
  README.md

Setiap bagian punya tanggung jawab yang berbeda: models.py mendefinisikan bentuk expense, storage.py berurusan dengan JSON, app.py berisi logic, dan main.py menjadi pintu masuk program. Jika satu aturan berubah, pembagian ini membantu kamu menemukan file yang relevan lebih cepat.

Buat virtual environment dan install pytest:

python -m venv .venv
source .venv/bin/activate
pip install pytest
pip freeze > requirements.txt

Di Windows, aktivasi .venv memakai command PowerShell:

.\.venv\Scripts\Activate.ps1

Model data dengan dataclass

Buat src/expense_tracker/models.py:

# File: models.py
from dataclasses import dataclass

@dataclass
class Expense:
    title: str
    amount: int

    def to_dict(self):
        return {
            "title": self.title,
            "amount": self.amount,
        }

Expense menyimpan data pengeluaran. Method to_dict() nanti dipakai saat data disimpan sebagai JSON.

Logic utama

Buat src/expense_tracker/app.py:

# File: app.py
from expense_tracker.models import Expense

def calculate_total(expenses):
    return sum(expense.amount for expense in expenses)

def format_currency(amount):
    return f"IDR {amount:,}".replace(",", ".")

def create_expense(title, amount_text):
    amount = int(amount_text)

    if amount <= 0:
        raise ValueError("Amount must be greater than zero")

    return Expense(title=title.strip(), amount=amount)

Function create_expense() sengaja menerima amount_text karena input dari terminal selalu berupa string. Jika input tidak valid, function akan melempar ValueError.

Storage JSON

Buat src/expense_tracker/storage.py:

# File: storage.py
import json
from pathlib import Path

from expense_tracker.models import Expense

def load_expenses(path):
    file_path = Path(path)

    if not file_path.exists():
        return []

    with file_path.open("r", encoding="utf-8") as file:
        data = json.load(file)

    return [
        Expense(title=item["title"], amount=item["amount"])
        for item in data
    ]

def save_expenses(path, expenses):
    file_path = Path(path)
    data = [expense.to_dict() for expense in expenses]

    with file_path.open("w", encoding="utf-8") as file:
        json.dump(data, file, indent=2)

File JSON mudah dibaca dan cukup untuk project pemula. Untuk aplikasi sungguhan, kamu mungkin akan memakai database, tapi jangan lompat terlalu cepat.

CLI loop

Buat src/expense_tracker/main.py:

# File: main.py
from expense_tracker.app import (
    calculate_total,
    create_expense,
    format_currency,
)
from expense_tracker.storage import load_expenses, save_expenses

DATA_FILE = "expenses.json"

def print_menu():
    print()
    print("1. Add expense")
    print("2. List expenses")
    print("3. Show total")
    print("4. Exit")

def add_expense(expenses):
    title = input("Title: ")
    amount_text = input("Amount: ")

    try:
        expense = create_expense(title, amount_text)
    except ValueError as error:
        print(f"Invalid input: {error}")
        return

    expenses.append(expense)
    save_expenses(DATA_FILE, expenses)
    print("Saved.")

def list_expenses(expenses):
    if not expenses:
        print("No expenses yet.")
        return

    for index, expense in enumerate(expenses, start=1):
        amount = format_currency(expense.amount)
        print(f"{index}. {expense.title} - {amount}")

def show_total(expenses):
    total = calculate_total(expenses)
    print(f"Total: {format_currency(total)}")

def main():
    expenses = load_expenses(DATA_FILE)

    while True:
        print_menu()
        choice = input("Choose: ")

        if choice == "1":
            add_expense(expenses)
        elif choice == "2":
            list_expenses(expenses)
        elif choice == "3":
            show_total(expenses)
        elif choice == "4":
            print("Bye.")
            break
        else:
            print("Unknown option.")

if __name__ == "__main__":
    main()

Jalankan dari root project:

python -m expense_tracker.main

Jika import belum terbaca karena struktur src/, jalankan dengan PYTHONPATH:

PYTHONPATH=src python -m expense_tracker.main

Di Windows PowerShell:

$env:PYTHONPATH="src"
python -m expense_tracker.main

Test logic total

Buat tests/test_app.py:

# File: test_app.py
from expense_tracker.app import calculate_total, create_expense
from expense_tracker.models import Expense

def test_calculate_total():
    expenses = [
        Expense("Coffee", 25_000),
        Expense("Lunch", 50_000),
    ]

    assert calculate_total(expenses) == 75_000

def test_create_expense():
    expense = create_expense("Coffee", "25000")

    assert expense.title == "Coffee"
    assert expense.amount == 25_000

Jalankan:

PYTHONPATH=src pytest

Test ini kecil, tapi penting. Kamu memastikan logic total dan parsing input tetap benar saat project berubah.

Perhatikan bahwa test tidak perlu menjalankan menu interaktif. Logic seperti calculate_total() dan create_expense() dibuat sebagai function terpisah supaya bisa dipanggil langsung oleh test. Inilah alasan struktur dan function dari artikel sebelumnya penting dalam project nyata.

Improvement ideas

Setelah versi minimal berjalan, coba tambah:

  • Delete expense.
  • Edit expense.
  • Category.
  • Filter by category.
  • Export ke CSV.
  • Monthly summary.
  • Argparse agar command bisa seperti expense add.

Tambahkan fitur satu per satu. Jangan langsung membuat semuanya sekaligus.

Kesalahan umum

Kesalahan pertama adalah menaruh semua kode di main.py. Untuk project kecil mungkin masih jalan, tapi cepat sulit dirawat.

Kesalahan kedua adalah mencampur input terminal dengan logic perhitungan. Pisahkan logic seperti calculate_total() agar mudah dites.

Kesalahan ketiga adalah tidak menangani input invalid. User bisa mengetik teks kosong, angka negatif, atau huruf di field amount.

Kesalahan keempat adalah terlalu cepat memakai database. Untuk belajar fundamental, JSON file sudah cukup.

Checklist sebelum menganggap selesai

Uji project dengan file JSON yang belum ada, file yang berisi list kosong, amount bernilai nol, amount berisi huruf, dan beberapa expense. Setiap kondisi seharusnya menghasilkan perilaku yang bisa dijelaskan, bukan traceback yang membingungkan.

Setelah itu, baca ulang pembagian module. Jika menu terlalu banyak mengandung logic perhitungan, pindahkan logic tersebut ke function di app.py. Jika storage.py mulai mengurus input terminal, pindahkan bagian itu kembali ke main.py. Batas kecil seperti ini membuat project tetap mudah dikembangkan.

Penutup seri

Kalau kamu menyelesaikan project ini, kamu sudah melewati jalur fundamental yang kuat. Kamu tidak hanya membaca sintaks Python; kamu sudah membuat program kecil dengan struktur, file storage, error handling, dan test.

Dari sini, jalur lanjutannya bisa berbeda-beda:

Fundamental yang kamu pakai di project ini akan muncul terus di semua jalur itu.

Kalau tujuanmu adalah menjadi backend developer dengan Python, jalur paling natural setelah seri ini adalah FastAPI. Di sana kamu akan memakai lagi function, dictionary, module, virtual environment, dependency, error handling, dan testing, tetapi konteksnya berubah dari aplikasi terminal menjadi API yang bisa dipanggil frontend atau service lain. Mulai dari artikel setup, lalu ikuti urutannya lewat Belajar FastAPI dari Nol: Rangkuman dan Jalur Belajar.

Untuk peta seluruh materi, ringkasan skill, dan checkpoint latihan terakhir, kunjungi Jalur Belajar Python Fundamental.

Lanjutkan membaca topik yang masih satu konteks.

Jangan Ketinggalan Info Terbaru

Dapatkan artikel teknologi, tips, dan insights menarik langsung ke email Kamu.