/how-to-build-lovable

How to build Expense tracking with Lovable?

Build an expense tracking app with Lovable: step by step setup data models, secure sync and integrations to automate reporting and control budgets

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free No-Code consultation

How to build Expense tracking with Lovable?

Build it as a small React app that stores expenses in Supabase (recommended) or localStorage for quick iteration. Use Lovable Chat Mode to create files, the Secrets UI to add SUPABASE_URL and SUPABASE_ANON\_KEY, Preview to test, and Publish when ready — no terminal needed. If you need DB migrations or server functions, create them in Supabase or export to GitHub for CLI work.

 

What we’re building / changing (plain English)

 

Expense tracker with a list view, add/edit/delete expense form, and persistence to Supabase (with a localStorage fallback for quick Preview). All work done via Lovable Chat Mode edits, Secrets UI and Preview.

 

Lovable-native approach

 

  • Use Chat Mode edits to create React components and a small API wrapper.
  • Use Lovable Secrets UI to add SUPABASE_URL and SUPABASE_ANON\_KEY for production persistence.
  • Preview to exercise UI against localStorage or Supabase.
  • Publish from Lovable Cloud when ready. For DB schema creation or server-side migrations use Supabase UI (outside Lovable) or export to GitHub for CLI work.

 

Meta-prompts to paste into Lovable (paste each Chat Mode message in order)

 

Prompt 1 — Scaffold UI and local persistence

 

// Goal: Create the basic expense UI and localStorage persistence so Preview works immediately
// Create or update these files with the exact content described

create src/App.tsx
// Simple router to open the Expenses page
import React from "react";
import Expenses from "./pages/Expenses";
export default function App(){ return <Expenses/>; }

create src/pages/Expenses.tsx
// Page: lists expenses and opens ExpenseForm
import React, {useEffect, useState} from "react";
import ExpenseForm from "../components/ExpenseForm";
import ExpenseItem from "../components/ExpenseItem";
import {loadExpenses, saveExpense, deleteExpense, updateExpense} from "../lib/localApi";
export default function Expenses(){
  const [items,setItems]=useState([]);
  const [editing,setEditing]=useState(null);
  useEffect(()=>{setItems(loadExpenses())},[]);
  async function add(e){ await saveExpense(e); setItems(loadExpenses()); }
  async function remove(id){ await deleteExpense(id); setItems(loadExpenses()); }
  async function edit(updated){ await updateExpense(updated); setItems(loadExpenses()); setEditing(null); }
  return (
    <div>
      <h1>Expenses</h1>
      <ExpenseForm onSubmit={add} initial={editing}/>
      <ul>{items.map(it=> <ExpenseItem key={it.id} item={it} onDelete={()=>remove(it.id)} onEdit={()=>setEditing(it)}/>)}</ul>
    </div>
  );
}

create src/components/ExpenseForm.tsx
// Form to add/edit. Keep minimal.
import React, {useState,useEffect} from "react";
export default function ExpenseForm({onSubmit, initial}){
  const [desc,setDesc]=useState(""); const [amount,setAmount]=useState("");
  useEffect(()=>{ if(initial){ setDesc(initial.description); setAmount(initial.amount); } },[initial]);
  function submit(e){ e.preventDefault(); onSubmit({id: initial?.id || Date.now().toString(), description:desc, amount:parseFloat(amount), date: new Date().toISOString()}); setDesc(""); setAmount(""); }
  return (
    <form onSubmit={submit}>
      <input value={desc} onChange={e=>setDesc(e.target.value)} placeholder="Description"/>
      <input value={amount} onChange={e=>setAmount(e.target.value)} placeholder="Amount"/>
      <button type="submit">Save</button>
    </form>
  );
}

create src/components/ExpenseItem.tsx
// Row with edit/delete actions
import React from "react";
export default function ExpenseItem({item,onDelete,onEdit}){
  return (<li>
    {item.description} — ${item.amount}
    <button onClick={onEdit}>Edit</button>
    <button onClick={onDelete}>Delete</button>
  </li>);
}

create src/lib/localApi.ts
// Simple localStorage API for Preview
export function loadExpenses(){ return JSON.parse(localStorage.getItem("expenses")||"[]"); }
export function saveExpense(exp){ const list=loadExpenses(); list.unshift(exp); localStorage.setItem("expenses",JSON.stringify(list)); }
export function deleteExpense(id){ const list=loadExpenses().filter(i=>i.id!==id); localStorage.setItem("expenses",JSON.stringify(list)); }
export function updateExpense(updated){ const list=loadExpenses().map(i=> i.id===updated.id?updated:i); localStorage.setItem("expenses",JSON.stringify(list)); }

 

Prompt 2 — Add Supabase client and switchable persistence

 

// Goal: Add Supabase client and API wrapper that uses Secrets when present
// Create/modify these files. Do NOT add secrets here — set them in Lovable Secrets UI.

create src/lib/supabaseClient.ts
// Create a client that reads from environment variables (Lovable Secrets)
import { createClient } from "@supabase/supabase-js";
// // These env values will be provided via Lovable Secrets at runtime
const SUPABASE_URL = process.env.SUPABASE_URL || "";
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || "";
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

modify src/lib/localApi.ts
// Update to try Supabase first when SUPABASE_URL & SUPABASE_ANON_KEY exist
import { supabase } from "./supabaseClient";
// keep local functions as fallback
export async function loadExpenses(){
  if(process.env.SUPABASE_URL && process.env.SUPABASE_ANON_KEY){
    const { data, error } = await supabase.from("expenses").select("*").order("created_at",{ascending:false});
    if(error) throw error; return data;
  }
  return JSON.parse(localStorage.getItem("expenses")||"[]");
}
export async function saveExpense(exp){
  if(process.env.SUPABASE_URL && process.env.SUPABASE_ANON_KEY){
    const { error } = await supabase.from("expenses").insert([{ id: exp.id, description: exp.description, amount: exp.amount, date: exp.date }]);
    if(error) throw error; return;
  }
  const list=JSON.parse(localStorage.getItem("expenses")||"[]"); list.unshift(exp); localStorage.setItem("expenses",JSON.stringify(list));
}
export async function deleteExpense(id){
  if(process.env.SUPABASE_URL && process.env.SUPABASE_ANON_KEY){
    const { error } = await supabase.from("expenses").delete().eq("id", id);
    if(error) throw error; return;
  }
  const list=JSON.parse(localStorage.getItem("expenses")||"[]").filter(i=>i.id!==id); localStorage.setItem("expenses",JSON.stringify(list));
}
export async function updateExpense(updated){
  if(process.env.SUPABASE_URL && process.env.SUPABASE_ANON_KEY){
    const { error } = await supabase.from("expenses").update({description:updated.description, amount:updated.amount}).eq("id", updated.id);
    if(error) throw error; return;
  }
  const list=JSON.parse(localStorage.getItem("expenses")||"[]").map(i=> i.id===updated.id?updated:i); localStorage.setItem("expenses",JSON.stringify(list));
}

 

Prompt 2 — Secrets setup instructions (Lovable Cloud)

 

// Goal: Add Supabase credentials using Lovable Secrets UI
// Steps for you (not pasting to chat):
// 1) Open Lovable Cloud > Secrets
// 2) Create SUPABASE_URL with your Supabase project URL
// 3) Create SUPABASE_ANON_KEY with the anon/public key
// 4) In Preview/Publish they will be available as process.env.SUPABASE_URL and SUPABASE_ANON_KEY

 

Prompt 3 — Guidance for Supabase table (outside Lovable)

 

// Goal: Create the 'expenses' table in Supabase (Supabase UI)
// This is outside Lovable: open your Supabase project and run SQL or use the table editor:
//
// create table expenses (
//   id text primary key,
//   description text,
//   amount numeric,
//   date timestamptz,
//   created_at timestamptz default now()
// );
//
// After creating the table the app will persist to Supabase when Secrets are present.

 

How to verify in Lovable Preview

 

  • Without Secrets: open Preview — add expenses; they persist via localStorage and survive reload.
  • With Secrets + Supabase table created: add an expense in Preview, then check Supabase table rows; the new row should appear. Also reload Preview to see listing from Supabase.

 

How to Publish / re-publish

 

  • Use Lovable Publish button. Ensure Secrets are set in Lovable Cloud before publishing so runtime can reach Supabase.
  • If you need to run DB migrations or server functions, do them in Supabase UI or export the repo to GitHub (Lovable GitHub sync) and run CLI migrations outside Lovable.

 

Common pitfalls in Lovable (and how to avoid them)

 

  • Forgetting to set Secrets — app silently falls back to localStorage; set SUPABASE_URL/ANON_KEY in Secrets UI.
  • Table not created — Supabase will return errors; create table in Supabase UI (outside Lovable).
  • Assuming terminal access — Lovable has no terminal. Any CLI migrations must be run outside Lovable (use GitHub export if needed).

 

Validity bar

 

  • The approach uses only Lovable-native actions (Chat Mode edits, Secrets, Preview, Publish). Supabase table creation is an external step and clearly labeled.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

How to detect duplicate expenses and suggest merges

This prompt helps an AI assistant understand your setup and guide to build the feature

AI AI Prompt

How to auto-suggest categories in Expense tracking

This prompt helps an AI assistant understand your setup and guide to build the feature

AI AI Prompt

How to preview CSV expense imports

This prompt helps an AI assistant understand your setup and guide to build the feature

AI AI Prompt

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free No-Code consultation

Best Practices for Building a Expense tracking with AI Code Generators

Build it as a secure, small-surface web app backed by a real DB (Supabase), use Lovable’s chat-first editor to generate and iterate code, keep secrets in Lovable’s Secrets UI, test in Preview, and export to GitHub for any local CLI or complex migration work. Focus on schema and auth first, validate all AI-generated code, enforce Row-Level Security in Supabase, and use Lovable-native actions (Chat edits, diffs/patches, Preview, Publish, Secrets, GitHub sync) rather than assuming you can run a terminal inside Lovable.

 

Planning & core decisions

 

Start small: define the minimal data model (users, expenses, categories, receipts) and auth flows before asking generators to scaffold UI or endpoints.

  • Pick a backend — Supabase is a great fit: Postgres, Auth, Storage, and a JS client.
  • Decide where code runs — server-side operations (sensitive keys, reporting) must run on backend; client uses anon keys + RLS.
  • Security first — plan RLS policies and never embed service\_role keys in client code.

 

Using Lovable effectively

 

Work within Lovable constraints: there’s no terminal, so use Chat Mode to edit files, Preview to run the app, Secrets UI to store env vars, and GitHub sync for anything requiring CLI (migrations, local testing).

  • Secrets UI: add SUPABASE_URL and SUPABASE_ANON_KEY (and SUPABASE_SERVICE\_ROLE only for server-side code via Lovable Secrets).
  • Chat edits & patches: ask the generator to create API routes and UI components; review diffs before applying.
  • Preview: validate flows end-to-end (signup, create expense, upload receipt) inside Lovable Preview.
  • GitHub sync: export to run migrations and CI/CD locally or on your own pipeline.

 

Practical security & data rules

 

  • Use Supabase RLS so each user can only access their expenses.
  • Validate all inputs server-side; never trust AI-generated client validation alone.
  • Store receipts in Supabase Storage with private buckets and signed URLs.

 

Example: safe serverless endpoint to create an expense (Node + Supabase JS)

 

// POST /api/expenses.js
// Expects body: { amount, currency, category_id, description }
// Uses SUPABASE_SERVICE_ROLE from Lovable Secrets (server-side)
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE
const sb = createClient(supabaseUrl, supabaseServiceKey)

export default async function handler(req, res) {
  // Reject non-POST
  if (req.method !== 'POST') return res.status(405).end()

  const { user_id, amount, currency, category_id, description } = req.body

  // Basic server-side validation
  if (!user_id || !amount || isNaN(amount)) return res.status(400).json({ error: 'invalid input' })

  // Insert expense as server (ensure user_id is valid)
  const { data, error } = await sb.from('expenses').insert([
    { user_id, amount, currency: currency || 'USD', category_id, description }
  ]).select()

  if (error) return res.status(500).json({ error: error.message })
  return res.status(201).json(data[0])
}

 

Operational tips and pitfalls

 

  • Don’t assume local CLI tasks work in Lovable: run DB migrations or seed scripts locally or via GitHub Actions after you export from Lovable.
  • Review AI output: AI will scaffold code fast but can miss edge cases (auth checks, sanitization). Manually audit diffs.
  • Use Preview frequently: small iterations in Lovable Preview catch integration issues early.
  • Monitor secrets: store keys in Lovable Secrets and never paste service keys into chat history.

 

Delivery & maintenance

 

  • Export to GitHub for CI/CD, database migrations, and full control.
  • Set up CI to run tests and apply migrations outside Lovable; deploy to your hosting of choice.
  • Keep schema and RLS policies under version control so generated changes are auditable.

 


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.