← Back to Blog
TutorialJanuary 5, 202512 min read

Building an Expense Tracker with Easy Enrichment

Step-by-step guide to building a fully-featured expense tracking app with automatic categorization.

Expense tracking is one of the most popular applications for transaction enrichment. In this tutorial, we'll build a complete expense tracker that automatically categorizes transactions, displays merchant logos, and generates spending reports. Check the final code in our examples page.

What We're Building

Our expense tracker will:

  • Import transactions from a bank or CSV file
  • Automatically categorize each transaction using our API
  • Display merchant logos for easy visual recognition
  • Show spending breakdown by category
  • Generate monthly expense reports
  • Detect and highlight subscriptions

Architecture Overview

User uploads transactions
        ↓
Backend receives raw data
        ↓
Batch enrich via Easy Enrichment API
        ↓
Store enriched transactions
        ↓
Display categorized expenses

Step 1: Set Up the API Client

First, let's create a reusable API client. Get your API key from the dashboard.

// lib/enrichment.js
const API_KEY = process.env.EASY_ENRICHMENT_API_KEY;
const BASE_URL = 'https://api.easyenrichment.com';

export async function enrichTransaction(description) {
  const response = await fetch(`${BASE_URL}/enrich`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ description })
  });
  return response.json();
}

export async function enrichBatch(transactions) {
  const response = await fetch(`${BASE_URL}/enrich/batch`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ transactions })
  });
  return response.json();
}

Step 2: Process Incoming Transactions

When a user uploads transactions, we process them in batches for efficiency:

// Process transactions in batches of 100
async function processTransactions(rawTransactions) {
  const enrichedTransactions = [];
  const batchSize = 100;

  for (let i = 0; i < rawTransactions.length; i += batchSize) {
    const batch = rawTransactions.slice(i, i + batchSize);
    const descriptions = batch.map(t => t.description);

    const result = await enrichBatch(descriptions);

    // Merge enriched data with original transaction data
    batch.forEach((tx, index) => {
      enrichedTransactions.push({
        ...tx,
        ...result.data[index],
        // Keep original amount and date
        amount: tx.amount,
        date: tx.date
      });
    });
  }

  return enrichedTransactions;
}

Step 3: Build the Category Breakdown

Our API returns a category field for each transaction. Here's how to aggregate spending by category:

function getCategoryBreakdown(transactions) {
  const breakdown = {};

  transactions.forEach(tx => {
    const category = tx.category || 'Uncategorized';

    if (!breakdown[category]) {
      breakdown[category] = {
        total: 0,
        count: 0,
        transactions: []
      };
    }

    breakdown[category].total += tx.amount;
    breakdown[category].count++;
    breakdown[category].transactions.push(tx);
  });

  // Sort by total spending
  return Object.entries(breakdown)
    .sort((a, b) => b[1].total - a[1].total)
    .map(([category, data]) => ({
      category,
      ...data,
      percentage: (data.total / transactions.reduce((sum, t) => sum + t.amount, 0)) * 100
    }));
}

Step 4: Create the Transaction List UI

Use the logo_url field to show merchant logos:

// React component
function TransactionList({ transactions }) {
  return (
    <div className="space-y-2">
      {transactions.map(tx => (
        <div key={tx.id} className="flex items-center gap-4 p-4 rounded-lg bg-zinc-900">
          {/* Merchant Logo */}
          <img
            src={tx.logo_url || '/placeholder-logo.png'}
            alt={tx.merchant_name}
            className="w-10 h-10 rounded-lg object-cover"
          />

          {/* Details */}
          <div className="flex-1">
            <div className="font-medium">{tx.merchant_name}</div>
            <div className="text-sm text-zinc-500">
              {tx.category}
              {tx.is_subscription && (
                <span className="ml-2 px-2 py-0.5 bg-purple-500/20 text-purple-400 rounded text-xs">
                  Subscription
                </span>
              )}
            </div>
          </div>

          {/* Amount */}
          <div className="text-right">
            <div className="font-medium">${tx.amount.toFixed(2)}</div>
            <div className="text-sm text-zinc-500">{formatDate(tx.date)}</div>
          </div>
        </div>
      ))}
    </div>
  );
}

Step 5: Generate Expense Reports

Combine everything to generate monthly expense reports. See the complete example in our examples section:

function generateMonthlyReport(transactions, month, year) {
  const monthlyTx = transactions.filter(tx => {
    const date = new Date(tx.date);
    return date.getMonth() === month && date.getFullYear() === year;
  });

  const breakdown = getCategoryBreakdown(monthlyTx);
  const subscriptions = monthlyTx.filter(tx => tx.is_subscription);
  const subscriptionTotal = subscriptions.reduce((sum, tx) => sum + tx.amount, 0);

  return {
    month: `${year}-${String(month + 1).padStart(2, '0')}`,
    totalSpending: monthlyTx.reduce((sum, tx) => sum + tx.amount, 0),
    transactionCount: monthlyTx.length,
    categoryBreakdown: breakdown,
    subscriptions: {
      count: new Set(subscriptions.map(tx => tx.merchant_name)).size,
      total: subscriptionTotal,
      list: [...new Set(subscriptions.map(tx => tx.merchant_name))]
    },
    topMerchants: getTopMerchants(monthlyTx, 5)
  };
}

Best Practices

  • Cache enrichments: Store enriched data to avoid re-processing the same transactions
  • Use batch endpoint: Process up to 100 transactions per API call for efficiency
  • Handle missing logos: Provide a fallback image when logo_url is null
  • Show confidence: Use the confidence score to highlight uncertain categorizations
  • Allow manual corrections: Let users override categories when needed

Extending the App

Once you have the basics, consider adding these features using other fields from our API:

  • Carbon tracking: Use co2_category to show environmental impact (learn more)
  • Subscription management: Use is_subscription and contact info (guide here)
  • Business expenses: Use MCC codes for tax categorization
  • Budget alerts: Notify when category spending exceeds limits

Start Building Today

Get your API key and 20 free calls to build your expense tracker. Check out our personal finance use case for more ideas.