Build a practical expense tracker from scratch that demonstrates real-world Vue 3 patterns including Pinia stores, computed totals, and Chart.js integration.
npm create vue@latest expense-tracker
cd expense-tracker
npm install pinia chart.js vue-chartjs
// stores/transactions.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useTransactionStore = defineStore('transactions', () => {
const transactions = ref([])
const income = computed(() =>
transactions.value.filter(t => t.amount > 0).reduce((s, t) => s + t.amount, 0)
)
const expenses = computed(() =>
transactions.value.filter(t => t.amount < 0).reduce((s, t) => s + t.amount, 0)
)
const balance = computed(() => income.value + expenses.value)
function addTransaction(transaction) {
transactions.value.push({
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
...transaction,
})
}
function removeTransaction(id) {
transactions.value = transactions.value.filter(t => t.id !== id)
}
return { transactions, income, expenses, balance, addTransaction, removeTransaction }
}, { persist: true })
<script setup>
import { reactive } from 'vue'
import { useTransactionStore } from '@/stores/transactions'
const store = useTransactionStore()
const form = reactive({ description: '', amount: '', category: 'food', type: 'expense' })
function submit() {
store.addTransaction({
description: form.description,
amount: form.type === 'expense' ? -Math.abs(form.amount) : Math.abs(form.amount),
category: form.category,
})
Object.assign(form, { description: '', amount: '' })
}
</script>
<script setup>
import { computed } from 'vue'
import { Doughnut } from 'vue-chartjs'
import { useTransactionStore } from '@/stores/transactions'
const store = useTransactionStore()
const chartData = computed(() => {
const categories = {}
store.transactions
.filter(t => t.amount < 0)
.forEach(t => { categories[t.category] = (categories[t.category] || 0) + Math.abs(t.amount) })
return {
labels: Object.keys(categories),
datasets: [{ data: Object.values(categories), backgroundColor: ['#FF6384','#36A2EB','#FFCE56','#4BC0C0'] }]
}
})
</script>
All Comments