A modern, privacy-focused expense tracking application built with React. All data is stored locally in your browser using SQLite (via sql.js) — no server, no account required.
- Income & Expense Tracking — Log transactions with categories, amounts, dates, and notes
- Dashboard Overview — View total income, expenses, and net balance at a glance
- Date Filtering — Filter summary totals by date range
- Custom Categories — Create and manage your own income/expense categories
- Multi-language Support — English and Turkish translations included
- Multi-currency Display — Format amounts in TRY, USD, or EUR
- Dark/Light Theme — Toggle between themes with automatic system preference detection
- Offline-first — Works entirely in the browser with SQLite persistence via localStorage
| Layer | Technology |
|---|---|
| Framework | React 19 |
| Build Tool | Vite 7 |
| Styling | Tailwind CSS 4 |
| Routing | React Router 7 |
| Database | SQLite (sql.js WebAssembly) |
| Icons | Lucide React |
# Clone the repository
git clone <repository-url>
cd expense-tracker
# Install dependencies
bun install
# or
npm install
# Start development server
bun dev
# or
npm run devThe app will be available at http://localhost:5173
| Command | Description |
|---|---|
bun dev |
Start development server with HMR |
bun build |
Build for production |
bun preview |
Preview production build locally |
bun lint |
Run ESLint |
bun format |
Format code with Prettier |
src/
├── main.jsx # Application entry point
├── App.jsx # Root component with theme/language controls
├── index.css # Global styles and Tailwind imports
│
├── components/ # Reusable UI components
│ ├── CategoryManager.jsx # Category CRUD panel (collapsible)
│ ├── EntryForm.jsx # Form for adding income/expense entries
│ ├── EntryTable.jsx # Table displaying all entries
│ ├── SummaryCards.jsx # Dashboard cards showing totals
│ └── ThemeToggle.jsx # Dark/light mode toggle button
│
├── contexts/ # React Context providers
│ ├── AppPreferences.jsx # Provider for language, currency, theme
│ ├── AppPreferencesContext.js # Context object
│ ├── appPreferencesConfig.js # Configuration constants
│ ├── translations.js # i18n translation strings
│ └── useAppPreferences.js # Hook to consume preferences context
│
├── hooks/ # Custom React hooks
│ ├── useCategories.js # Category state management
│ └── useEntries.js # Entry state management with totals
│
├── lib/ # Utilities and services
│ └── db.js # SQLite database initialization & queries
│
└── pages/ # Page components
└── TrackerPage.jsx # Main tracker page with all sections
The app uses sql.js, a WebAssembly port of SQLite, to store data directly in the browser's localStorage.
entries table:
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key, auto-increment |
| type | TEXT | 'income' or 'expense' |
| category | TEXT | Category name |
| amount | REAL | Transaction amount |
| date | TEXT | ISO date string (YYYY-MM-DD) |
| notes | TEXT | Optional notes |
categories table:
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key, auto-increment |
| name | TEXT | Category name |
| type | TEXT | 'income' or 'expense' |
import { getDatabase, persistDatabase, fetchEntries, fetchCategories } from './lib/db'
// Initialize database (async, returns singleton)
const db = await getDatabase()
// Fetch all entries
const entries = fetchEntries(db)
// Persist changes to localStorage
persistDatabase(db)Manages expense/income entries with automatic total calculation.
const { entries, totals, loading, error, addEntry, removeEntry } = useEntries()
// totals = { income: number, expense: number, net: number }Manages custom categories.
const { categories, loading, error, addCategory, removeCategory } = useCategories()Provides internationalization and formatting utilities.
const {
language, // Current language code ('en' | 'tr')
setLanguage, // Change language
currency, // Current currency code ('TRY' | 'USD' | 'EUR')
setCurrency, // Change currency
t, // Translation function: t('section.key')
formatCurrency, // Format number as currency: formatCurrency(100)
languageOptions, // Available languages
currencyOptions, // Available currencies
} = useAppPreferences()Edit src/contexts/translations.js to add or modify translation strings:
export const translations = {
en: {
mySection: {
myKey: 'English text',
},
},
tr: {
mySection: {
myKey: 'Turkish text',
},
},
}Then use in components:
const { t } = useAppPreferences()
return <p>{t('mySection.myKey')}</p>Displays three cards: Total Income, Total Expense, and Net Difference.
Form for creating new entries. Validates amount, category, and date before submission.
Sortable table displaying all entries with delete functionality.
Collapsible panel for managing custom categories. Grouped by income/expense type.
Button to switch between light and dark themes.
All data is stored in your browser's localStorage under the key expense-tracker-sqlite. To reset all data, clear this key from your browser's developer tools:
localStorage.removeItem('expense-tracker-sqlite')Then refresh the page.
MIT