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 expensesStep 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.