DEV Community

Gokul Gokul
Gokul Gokul

Posted on

I Built a Full-Stack Personal Finance Tracker β€” Here's What Makes It Different πŸ’°

πŸ”— Live App: spendsmart.gokulmithran.com


Most personal finance apps on the internet fall into one of two camps: either they're pretty UIs with a Firebase backend and zero security thinking, or they're enterprise monsters that take 6 months to set up. SpendSmart is neither.

I built SpendSmart as a production-grade, full-stack personal finance tracker with the same security and performance patterns you'd find in a real fintech product β€” deployed, live, and free to use.

Here's the deep dive into what I built, how I built it, and why the technical decisions matter.


🧩 What is SpendSmart?

SpendSmart is a web-based personal finance application that lets users:

  • Track transactions (income & expenses) with category tagging and filtering
  • Set budgets per spending category and get visual progress tracking
  • Create savings goals with contributions, withdrawals, and milestone tracking
  • View detailed reports β€” monthly breakdowns, annual overviews, daily spending trends, category donut charts, CSV export, and auto-generated insights
  • Manage multiple accounts (bank accounts, wallets, credit cards)
  • Receive push notifications for budget alerts and goal milestones
  • Install as a PWA β€” works offline and behaves like a native mobile app

πŸ—οΈ Tech Stack at a Glance

Layer Technology
Frontend React 18 + TypeScript + Vite 7
Styling Tailwind CSS 4 + Radix UI primitives
State TanStack React Query v5
Charts Recharts (Bar, Line, Pie, Donut)
Animations Framer Motion
Forms React Hook Form + Zod validation
PWA Workbox (vite-plugin-pwa) + Service Worker
Backend Spring Boot 3.5 + Java 21
Database PostgreSQL 15
ORM Spring Data JPA + MapStruct DTOs
Auth Spring Security + OAuth2 (Google)
Rate Limiting Bucket4j + Caffeine Cache
Notifications Web Push (RFC 8291) + BouncyCastle
Observability Micrometer + Prometheus + Spring Actuator
API Docs Springdoc OpenAPI (Swagger UI)
Secrets AWS Parameter Store
Infra Docker + Docker Compose
CI/CD GitHub Actions (AWS + GCP deploy pipelines)
CDN/Hosting Cloudflare

πŸ” Security β€” Not an Afterthought

This is where SpendSmart diverges from most side-project expense trackers. I treated security like a first-class citizen from Day 1.

OAuth2 with Session-Based Auth (No JWTs)

Most tutorials push JWT-based auth for SPAs. I went with session-based authentication with Spring Security OAuth2 instead. Why?

  • Server-side session invalidation β€” if a user logs out, the session is dead immediately. No waiting for token expiry.
  • CSRF protection built-in via CookieCsrfTokenRepository with SameSite=None; Secure cookies in production
  • Session fixation protection via migrateSession()
  • Single-session enforcement β€” one session per user, period
http.sessionManagement(session -> session
    .sessionFixation().migrateSession()
    .maximumSessions(1));
Enter fullscreen mode Exit fullscreen mode

Production Security Headers

The prod security config sets a tight CSP, disables iframes, enforces HSTS with preload, and strips referrers:

http.headers(headers -> headers
    .contentSecurityPolicy(csp -> csp.policyDirectives(
        "default-src 'self'; script-src 'self'; ..."))
    .frameOptions(FrameOptionsConfig::deny)
    .httpStrictTransportSecurity(hsts -> hsts
        .includeSubDomains(true)
        .maxAgeInSeconds(31536000)
        .preload(true))
    .referrerPolicy(referrer -> referrer
        .policy(ReferrerPolicy.NO_REFERRER)));
Enter fullscreen mode Exit fullscreen mode

Per-Endpoint Rate Limiting with Bucket4j

I didn't slap a global rate limiter and call it a day. SpendSmart uses a policy-based rate limiting system where different API endpoint groups have different limits:

Endpoint Group Limit Key Strategy
Auth (/oauth2/, /login/) 5 req/min IP-based
Dashboard 120 req/min User-based
Transactions 60 req/min User-based
Budgets 30 req/min User-based
Goals 20 req/min User-based
Default API 100 req/min User-based

Auth endpoints are rate-limited by IP (to prevent brute-force attacks on login flows), while all other endpoints are rate-limited by authenticated user. The policies are backed by Caffeine in-memory cache for near-zero-latency bucket lookups.


πŸ“Š Reports & Insights β€” More Than Just Charts

The Reports module isn't a simple "show me a pie chart" screen. It provides:

  • Annual overview β€” stacked bar chart comparing income vs. expenses across all 12 months
  • Monthly summary cards β€” income, expenses, net savings, savings rate percentage with month-over-month change percentages
  • Daily spending trend β€” line chart showing spending patterns within a month
  • Category breakdown β€” interactive donut chart with percentage splits
  • Top spending categories β€” ranked list with progress bars
  • Budget performance β€” shows how each budget performed for the month
  • Auto-generated insights β€” the backend computes contextual financial insights
  • CSV export β€” one-click download of monthly transaction data

All of this is computed server-side in a single optimized query pass with batch fetching to avoid N+1 problems:

private Map<Long, BigDecimal> batchFetchSpentAmounts(List<Budget> budgets, User user) {
    List<Long> categoryIds = budgets.stream()
        .map(b -> b.getCategory().getId())
        .distinct().collect(Collectors.toList());

    List<Object[]> results = transactionRepository
        .findTotalSpentPerCategory(user, categoryIds, start, end);

    return results.stream().collect(Collectors.toMap(
        r -> (Long) r[0], r -> (BigDecimal) r[1]));
}
Enter fullscreen mode Exit fullscreen mode

πŸ“± PWA with Real Push Notifications

SpendSmart isn't just "responsive" β€” it's a full Progressive Web App:

  • Workbox-powered service worker with precaching and offline navigation
  • Web Push notifications using RFC 8291 (VAPID keys + BouncyCastle encryption)
  • Backend has a dedicated notification subsystem with:
    • NotificationDispatcher β€” routes notifications
    • NotificationEventPublisher β€” event-driven architecture
    • NotificationRateLimiter β€” prevents notification spam
    • PushNotificationService β€” sends encrypted push payloads

The service worker handles push events and notification clicks with deep-link routing:

self.addEventListener('push', (event) => {
    const data = event.data.json();
    self.registration.showNotification(data.title, {
        body: data.body,
        icon: '/favicon.png',
        vibrate: [100, 50, 100],
        data: { url: data.url || '/' }
    });
});
Enter fullscreen mode Exit fullscreen mode

🎨 Frontend Architecture

React Query for Server State

No Redux. No Zustand. TanStack React Query v5 handles all server state β€” with automatic caching, background refetching, optimistic updates, and stale-while-revalidate patterns. The result is a UI that feels instant.

Component Architecture

  • Radix UI primitives (Dialog, Select, Popover, AlertDialog) for accessibility-first components
  • Framer Motion for page transitions and micro-animations
  • Recharts for responsive data visualizations
  • React Hook Form + Zod for type-safe form validation with zero re-renders
  • Custom undo-action hook β€” delete operations show a toast with an "Undo" option before actually firing the API call

Context-driven Theming

Four React Contexts power the UX:

  • AuthContext β€” authentication state
  • CurrencyContext β€” locale-aware currency formatting
  • ThemeContext β€” dark/light mode with system preference detection
  • SidebarContext β€” responsive sidebar state

☁️ Deployment & Infrastructure

  • Dockerized backend + frontend + PostgreSQL via Docker Compose
  • GitHub Actions CI/CD with separate workflows for AWS and GCP deployments
  • AWS Parameter Store for secret management (no .env files in production)
  • Cloudflare for CDN, DDoS protection, and analytics
  • Spring Boot Actuator + Prometheus metrics endpoint for production observability

πŸ’‘ What I'd Do Differently

  1. Add Redis β€” Caffeine works great for single-instance rate limiting, but for horizontal scaling I'd swap to Redis-backed Bucket4j
  2. Implement recurring transactions β€” auto-detect salary, rent, subscriptions
  3. Add bank connectivity β€” integrate with Plaid or a similar aggregator
  4. Mobile app β€” the PWA is solid, but a React Native companion would unlock background sync and native widgets

πŸ”— Try It Out

Live: spendsmart.gokulmithran.com

Login with Google, add a few transactions, and see how it feels. It's free, your data is yours, and no credit card is needed.


If this was helpful, drop a ❀️ and follow for more full-stack deep dives. Questions? Hit me in the comments!


Tags: #webdev #react #java #springboot

Top comments (0)