TL;DR: You can build a fully functional expense tracker with AI in about an hour — add expenses with categories, see monthly summaries with charts, and export everything to CSV. The stack: Next.js + Supabase + Recharts + Tailwind CSS. This project teaches you database CRUD operations, aggregation queries, and data visualization — three skills you'll use in every future project. Copy the prompts below, and you'll have a working app before lunch.
Why an Expense Tracker Is the Perfect First Project
Every "learn to code" course starts with a to-do app. That's fine for understanding the basics, but a to-do app doesn't teach you anything useful about building real software. An expense tracker does.
Here's what you'll actually learn by building this:
- Database CRUD — Create, Read, Update, Delete. Every app you'll ever build does these four things. An expense tracker makes them concrete: add an expense, list expenses, edit a mistake, delete a duplicate.
- Aggregation queries — "Show me total spending by category this month." That's a real SQL aggregation query, and it's the foundation of dashboards, reports, and analytics in any app.
- Data visualization — Turning raw numbers into charts that actually mean something. Pie charts for category breakdowns, bar charts for monthly trends. This is the skill that makes dashboards look professional.
- Data export — Generating a CSV file from database records. Sounds simple, but it touches file generation, data formatting, and browser downloads — patterns you'll reuse constantly.
And here's the practical reason: you'll actually use this app. A to-do app gets deleted. An expense tracker you built yourself — with exactly the categories and views you care about — that becomes part of your daily life. There's no better motivation to maintain and improve a project than actually depending on it.
Next.js gives you React with built-in routing and server-side features. Supabase gives you a real PostgreSQL database with a generous free tier. Recharts turns your data into interactive charts with minimal code. AI generates excellent code for this combination because it's one of the most popular stacks in 2026. You'll get working code on the first prompt more often than with any other combo.
The Prompt That Builds Your Expense Tracker
This is the prompt that kicks everything off. One prompt, and AI generates a complete working application. Copy it exactly:
Build me a personal expense tracker app with these features:
Tech stack: Next.js 14 with App Router, TypeScript, Tailwind CSS, Supabase for the database, and Recharts for charts.
Core features:
- Add expenses with: amount (dollars), category (dropdown: Food, Transport, Housing, Utilities, Entertainment, Shopping, Health, Education, Other), description (optional text), and date
- List all expenses in a sortable table with edit and delete buttons
- Monthly summary dashboard showing: total spent, average daily spend, top category, and number of transactions
- Two charts: a pie chart showing spending by category and a bar chart showing daily spending for the current month
- Month picker to switch between months
- Export current month's expenses to CSV
Database: Create a Supabase table called "expenses" with columns: id (uuid, primary key), amount (numeric), category (text), description (text), expense_date (date), created_at (timestamp with timezone).
Design: Clean, modern dashboard layout with a dark theme. Responsive — works on mobile. Use a card-based layout for the summary stats at the top.
That's it. Paste this into Claude, Cursor, or whatever AI coding tool you're using. In about 30 seconds, you'll get a complete project with every feature listed above.
But — and this is important — don't just accept the output blindly. Let's walk through what AI generates, what each piece does, and where things typically go wrong.
What AI Generated — And What Each Piece Does
After that prompt, AI creates roughly 8-12 files. Here's what you'll see and why each piece matters:
The Database Schema
-- Supabase SQL Editor: run this to create your table
CREATE TABLE expenses (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
amount NUMERIC(10,2) NOT NULL,
category TEXT NOT NULL,
description TEXT,
expense_date DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for fast monthly queries
CREATE INDEX idx_expenses_date ON expenses(expense_date);
What this does: Creates a single table in your Supabase database to store every expense. Each expense gets a unique ID (UUID), an amount stored to two decimal places, a category, an optional description, and a date. The index at the bottom makes monthly queries fast — without it, the database would scan every row to find expenses in a given month.
If you've never run SQL before: go to your Supabase dashboard, click "SQL Editor" in the sidebar, paste this in, and hit "Run." That's it. Your database is ready. For more on how database changes work, see what database migrations are.
The Supabase Client
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseKey)
What this does: Connects your app to your Supabase database. The URL and key come from your Supabase project settings (Settings → API). The NEXT_PUBLIC_ prefix means these values are available in the browser — that's fine for the anonymous key (it's meant to be public), but never put your service role key here.
The Expense Form Component
AI generates a form component that handles adding new expenses. The key parts:
// The insert operation — this is CRUD's "Create"
const { error } = await supabase
.from('expenses')
.insert({
amount: parseFloat(amount),
category,
description,
expense_date: date
});
What this does: Takes the form values and inserts a new row into your expenses table. supabase.from('expenses').insert() is the Supabase way of saying INSERT INTO expenses VALUES (...). This is the "C" in CRUD — Create.
The Expense List
// Fetching expenses for a specific month — this is CRUD's "Read"
const { data, error } = await supabase
.from('expenses')
.select('*')
.gte('expense_date', startOfMonth)
.lte('expense_date', endOfMonth)
.order('expense_date', { ascending: false });
What this does: Fetches all expenses between the first and last day of the selected month, newest first. .gte() means "greater than or equal to" and .lte() means "less than or equal to" — together they create a date range filter. This is the "R" in CRUD — Read.
The Monthly Summary (Aggregation)
This is where it gets interesting. AI generates code that calculates summary stats from your expense data:
// Calculate monthly totals — aggregation in action
const totalSpent = expenses.reduce((sum, exp) => sum + exp.amount, 0);
const avgDaily = totalSpent / daysInMonth;
const byCategory = expenses.reduce((acc, exp) => {
acc[exp.category] = (acc[exp.category] || 0) + exp.amount;
return acc;
}, {} as Record<string, number>);
const topCategory = Object.entries(byCategory)
.sort(([,a], [,b]) => b - a)[0]?.[0] || 'None';
What this does: Takes your raw list of expenses and calculates four useful numbers: total spending, daily average, spending by category, and which category you spent the most in. The .reduce() function is JavaScript's way of aggregating a list into a single value — AI uses it constantly, and understanding this one pattern unlocks dashboards, reports, and analytics in any app you build.
The Charts
// Pie chart for category breakdown
<PieChart>
<Pie data={categoryData} dataKey="value" nameKey="name">
{categoryData.map((entry, index) => (
<Cell key={index} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip formatter={(value) => `$${value.toFixed(2)}`} />
<Legend />
</PieChart>
What this does: Takes your spending-by-category data and turns it into a visual pie chart. Recharts handles all the SVG rendering — you just pass it data in the right shape. categoryData is an array like [{ name: "Food", value: 342.50 }, { name: "Transport", value: 128.00 }]. The Tooltip shows dollar amounts when you hover. The Legend shows which color is which category.
The CSV Export
// Generate and download CSV
function exportToCSV(expenses: Expense[]) {
const headers = ['Date', 'Category', 'Amount', 'Description'];
const rows = expenses.map(exp => [
exp.expense_date,
exp.category,
exp.amount.toFixed(2),
exp.description || ''
]);
const csv = [headers, ...rows]
.map(row => row.map(cell =>
`"${String(cell).replace(/"/g, '""')}"`
).join(','))
.join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `expenses-${selectedMonth}.csv`;
a.click();
}
What this does: Takes your expense data, formats it as comma-separated values with proper quoting (handles descriptions that contain commas), creates a downloadable file in the browser, and triggers a download. No server needed — this all happens client-side. The .replace(/"/g, '""') part escapes double quotes inside cell values, which is how CSV handles special characters.
The Concepts You Just Learned
Without even trying, you've now worked with five fundamental building blocks of software development. Let's name them so you recognize them in every future project:
| Concept | What It Is | Where You Used It |
|---|---|---|
| CRUD Operations | Create, Read, Update, Delete — the four database operations | Adding, listing, editing, deleting expenses |
| Data Aggregation | Combining many records into summary statistics | Monthly totals, category breakdowns, daily averages |
| Data Visualization | Turning numbers into charts humans can read | Pie chart (categories), bar chart (daily spending) |
| Client-Side Export | Generating files in the browser without a server | CSV download button |
| Date Range Filtering | Querying data within a time window | Month picker filtering expenses by date |
These aren't expense-tracker-specific skills. Every dashboard, every analytics page, every reporting feature in every SaaS app uses exactly these patterns. When you move on to building a full SaaS app, you'll recognize all of them.
What AI Gets Wrong About Expense Trackers
AI will generate a working expense tracker on the first try about 80% of the time. Here's where the other 20% goes sideways — and how to spot it before it bites you:
This is the most common mistake. AI sometimes stores amounts as JavaScript number type without proper handling. Floating point math means 0.1 + 0.2 = 0.30000000000000004 in JavaScript. Your database column should be NUMERIC(10,2) (which stores exact decimals), and you should always use .toFixed(2) when displaying amounts. If your monthly totals look like $1,247.5300000001 — this is why.
You add an expense on March 31st at 11 PM Pacific time. AI stores it as a UTC timestamp — which is April 1st in UTC. Now your March report is missing an expense, and April has one that shouldn't be there. The fix: use DATE type instead of TIMESTAMP for expense_date (which is what our schema does), and handle timezone conversion on the client side when creating the date string.
AI often skips validating the amount field. Without validation, users can enter negative numbers (a refund? or a bug?), zero-dollar expenses, or amounts with more than two decimal places. Add validation: amount must be positive, maximum two decimal places, and set a reasonable max (like $99,999.99) to prevent typos that wreck your monthly charts.
If you're building this just for yourself, it doesn't matter. But the moment you add authentication for multiple users, you need Supabase Row Level Security (RLS) policies. Without them, any authenticated user can read every user's expenses. AI frequently generates the auth code but forgets the RLS policies. Ask specifically: "Add RLS policies so users can only see their own expenses."
Your charts work great with data. Switch to a month with zero expenses and — crash. Recharts throws an error when you pass it an empty array. AI often doesn't handle this edge case. Always check: if (expenses.length === 0) and show an empty state message instead of trying to render a chart with no data.
How to Debug When Things Break
Your expense tracker will break. Every project does. Here's how to figure out what went wrong without panicking:
Expenses Not Saving
- Check the browser console (right-click → Inspect → Console tab). Look for red error messages.
- Check Supabase. Go to your Supabase dashboard → Table Editor → expenses. Is the table there? Are rows appearing?
- Check your environment variables. The most common cause of "nothing works" is a missing or wrong
.env.localfile. You needNEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEY. - Copy the exact error message and paste it back to your AI tool: "I'm getting this error when I try to add an expense: [error]. Here's my code. What's wrong?"
Charts Not Rendering
- Check if Recharts is installed. Run
npm list rechartsin your terminal. If it's not listed, runnpm install recharts. - Check the data shape. Console.log the data you're passing to the chart. Recharts expects
[{ name: "Food", value: 150 }]— if your data looks different, the chart won't render. - Check for the empty data bug mentioned above. Add a guard: don't render the chart component if there's no data.
Monthly Totals Look Wrong
- Check the floating point issue. Are your numbers showing too many decimal places? That's the
NUMERICvsFLOATissue. - Check the date range filter. Console.log your
startOfMonthandendOfMonthvalues. Are they correct? Is the timezone offset causing off-by-one-day errors? - Check Supabase directly. Run this query in the SQL Editor:
SELECT SUM(amount) FROM expenses WHERE expense_date BETWEEN '2026-03-01' AND '2026-03-31';If the database total matches your app, the query is fine — the bug is in your display code.
When something breaks, your job isn't to fix it — it's to figure out where it breaks. Is the data wrong in the database? Or is it correct in the database but displayed wrong in the app? Narrowing down which layer has the problem is 80% of debugging. AI can fix any bug once you tell it exactly what's happening and where.
Stretch Goals: Make It Yours
Got the basic tracker working? Here's how to level it up. Each of these is a single prompt away:
🔐 Add Authentication
Turn your personal tracker into a multi-user app. Prompt: "Add Supabase Auth with email/password login. Add a user_id column to the expenses table. Add RLS policies so users can only CRUD their own expenses. Create login and signup pages."
🏷️ Custom Categories
Let users create their own categories instead of using the preset list. Prompt: "Add a categories table (id, name, color, user_id) and let users manage their own categories. Replace the hardcoded dropdown with categories from the database. Include a color picker for chart colors."
📊 Year-Over-Year Comparison
Show spending trends across months. Prompt: "Add a line chart showing total monthly spending for the past 12 months. Include a comparison overlay showing the same month last year if data exists."
🔄 Recurring Expenses
Rent, subscriptions, utilities — things that repeat every month. Prompt: "Add a recurring expenses feature. Users can mark an expense as recurring (monthly, weekly, or yearly). Automatically create new expense entries on the recurrence date. Show recurring vs one-time expenses in the summary."
📱 Progressive Web App (PWA)
Make it installable on your phone. Prompt: "Convert this Next.js app into a PWA with a service worker, manifest.json, and offline support. It should be installable on iOS and Android home screens."
🤖 AI-Powered Categorization
Type "Starbucks $5.50" and let AI figure out it's Food. Prompt: "Add an AI categorization feature. When the user types a description, use the OpenAI API to suggest a category automatically. Show the suggestion as a pre-selected dropdown value that the user can override."
Each stretch goal teaches you a new skill. Authentication teaches you user management. Custom categories teach you relational data (two tables linked together). The PWA goal teaches you deployment for mobile. Pick whatever sounds most useful to you and prompt for it.
Frequently Asked Questions
About 60-90 minutes for a working version with categories, monthly summaries, and basic charts. Add another hour for CSV export, polished UI, and mobile responsiveness. AI handles the repetitive CRUD code instantly — your time goes into describing what you want and testing the results.
No. You need to understand what you want the app to do, and you need to be able to read error messages when things break. AI generates all the JavaScript, React components, and database queries. This guide teaches you what each piece does so you can debug and extend it — not so you can write it from scratch.
Local storage disappears when you clear your browser or switch devices. Supabase gives you a real PostgreSQL database — your data persists, you can access it from any device, and you learn database skills that apply to every future project. The free tier handles 500MB of data, which is years of expense tracking.
Absolutely — that's one of the stretch goals above. Supabase includes built-in authentication with email/password and social logins. Add auth, attach a user_id to each expense, and filter queries by the logged-in user. AI handles this pattern well because it's one of the most common in web development.
Zero dollars. Vercel's free tier hosts the Next.js frontend, Supabase's free tier provides the database (500MB storage, 50,000 monthly active users), and there's nothing else to pay for. You could run this app for years without spending a cent on infrastructure.