<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gokul Gokul</title>
    <description>The latest articles on DEV Community by Gokul Gokul (@gokul_mithran_dev).</description>
    <link>https://dev.to/gokul_mithran_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3818843%2F35e06ffb-0f6f-479e-9784-88a3a2bd3ab9.png</url>
      <title>DEV Community: Gokul Gokul</title>
      <link>https://dev.to/gokul_mithran_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gokul_mithran_dev"/>
    <language>en</language>
    <item>
      <title>I Built a Full-Stack Personal Finance Tracker — Here's What Makes It Different 💰</title>
      <dc:creator>Gokul Gokul</dc:creator>
      <pubDate>Thu, 12 Mar 2026 04:46:26 +0000</pubDate>
      <link>https://dev.to/gokul_mithran_dev/i-built-a-full-stack-personal-finance-tracker-heres-what-makes-it-different-2j58</link>
      <guid>https://dev.to/gokul_mithran_dev/i-built-a-full-stack-personal-finance-tracker-heres-what-makes-it-different-2j58</guid>
      <description>&lt;p&gt;&lt;strong&gt;🔗 Live App: &lt;a href="https://spendsmart.gokulmithran.com/" rel="noopener noreferrer"&gt;spendsmart.gokulmithran.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;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. &lt;strong&gt;SpendSmart&lt;/strong&gt; is neither.&lt;/p&gt;

&lt;p&gt;I built SpendSmart as a &lt;strong&gt;production-grade, full-stack personal finance tracker&lt;/strong&gt; with the same security and performance patterns you'd find in a real fintech product — deployed, live, and free to use.&lt;/p&gt;

&lt;p&gt;Here's the deep dive into &lt;em&gt;what&lt;/em&gt; I built, &lt;em&gt;how&lt;/em&gt; I built it, and &lt;em&gt;why&lt;/em&gt; the technical decisions matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 What is SpendSmart?
&lt;/h2&gt;

&lt;p&gt;SpendSmart is a web-based personal finance application that lets users:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  🏗️ Tech Stack at a Glance
&lt;/h2&gt;

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




&lt;h2&gt;
  
  
  🔐 Security — Not an Afterthought
&lt;/h2&gt;

&lt;p&gt;This is where SpendSmart diverges from most side-project expense trackers. I treated security like a first-class citizen from Day 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth2 with Session-Based Auth (No JWTs)
&lt;/h3&gt;

&lt;p&gt;Most tutorials push JWT-based auth for SPAs. I went with &lt;strong&gt;session-based authentication with Spring Security OAuth2&lt;/strong&gt; instead. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-side session invalidation&lt;/strong&gt; — if a user logs out, the session is dead &lt;em&gt;immediately&lt;/em&gt;. No waiting for token expiry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSRF protection&lt;/strong&gt; built-in via &lt;a&gt;CookieCsrfTokenRepository&lt;/a&gt; with &lt;code&gt;SameSite=None; Secure&lt;/code&gt; cookies in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session fixation protection&lt;/strong&gt; via &lt;code&gt;migrateSession()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Single-session enforcement — &lt;strong&gt;one session per user&lt;/strong&gt;, period
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sessionManagement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sessionFixation&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;migrateSession&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maximumSessions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production Security Headers
&lt;/h3&gt;

&lt;p&gt;The prod security config sets a tight CSP, disables iframes, enforces HSTS with preload, and strips referrers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentSecurityPolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csp&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;csp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policyDirectives&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"default-src 'self'; script-src 'self'; ..."&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;frameOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;FrameOptionsConfig:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;httpStrictTransportSecurity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hsts&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hsts&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;includeSubDomains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;maxAgeInSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;31536000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;referrerPolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;referrer&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;referrer&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReferrerPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NO_REFERRER&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Per-Endpoint Rate Limiting with Bucket4j
&lt;/h3&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint Group&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Key Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auth (&lt;code&gt;/oauth2/&lt;/code&gt;, &lt;code&gt;/login/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;5 req/min&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;IP-based&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;120 req/min&lt;/td&gt;
&lt;td&gt;User-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions&lt;/td&gt;
&lt;td&gt;60 req/min&lt;/td&gt;
&lt;td&gt;User-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Budgets&lt;/td&gt;
&lt;td&gt;30 req/min&lt;/td&gt;
&lt;td&gt;User-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goals&lt;/td&gt;
&lt;td&gt;20 req/min&lt;/td&gt;
&lt;td&gt;User-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default API&lt;/td&gt;
&lt;td&gt;100 req/min&lt;/td&gt;
&lt;td&gt;User-based&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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 &lt;strong&gt;Caffeine in-memory cache&lt;/strong&gt; for near-zero-latency bucket lookups.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Reports &amp;amp; Insights — More Than Just Charts
&lt;/h2&gt;

&lt;p&gt;The Reports module isn't a simple "show me a pie chart" screen. It provides:&lt;/p&gt;

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

&lt;p&gt;All of this is computed server-side in a single optimized query pass with batch fetching to avoid N+1 problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;batchFetchSpentAmounts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Budget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;budgets&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;categoryIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;budgets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCategory&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[]&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transactionRepository&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findTotalSpentPerCategory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;categoryIds&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📱 PWA with Real Push Notifications
&lt;/h2&gt;

&lt;p&gt;SpendSmart isn't just "responsive" — it's a full &lt;strong&gt;Progressive Web App&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workbox-powered service worker&lt;/strong&gt; with precaching and offline navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Push notifications&lt;/strong&gt; using RFC 8291 (VAPID keys + BouncyCastle encryption)&lt;/li&gt;
&lt;li&gt;Backend has a dedicated notification subsystem with:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NotificationDispatcher&lt;/code&gt; — routes notifications&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NotificationEventPublisher&lt;/code&gt; — event-driven architecture&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NotificationRateLimiter&lt;/code&gt; — prevents notification spam&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PushNotificationService&lt;/code&gt; — sends encrypted push payloads&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The service worker handles push events and notification clicks with deep-link routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/favicon.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;vibrate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🎨 Frontend Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  React Query for Server State
&lt;/h3&gt;

&lt;p&gt;No Redux. No Zustand. &lt;strong&gt;TanStack React Query v5&lt;/strong&gt; handles all server state — with automatic caching, background refetching, optimistic updates, and stale-while-revalidate patterns. The result is a UI that feels instant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component Architecture
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Context-driven Theming
&lt;/h3&gt;

&lt;p&gt;Four React Contexts power the UX:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AuthContext&lt;/code&gt; — authentication state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CurrencyContext&lt;/code&gt; — locale-aware currency formatting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ThemeContext&lt;/code&gt; — dark/light mode with system preference detection&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SidebarContext&lt;/code&gt; — responsive sidebar state&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ☁️ Deployment &amp;amp; Infrastructure
&lt;/h2&gt;

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




&lt;h2&gt;
  
  
  💡 What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add Redis&lt;/strong&gt; — Caffeine works great for single-instance rate limiting, but for horizontal scaling I'd swap to Redis-backed Bucket4j&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement recurring transactions&lt;/strong&gt; — auto-detect salary, rent, subscriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add bank connectivity&lt;/strong&gt; — integrate with Plaid or a similar aggregator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile app&lt;/strong&gt; — the PWA is solid, but a React Native companion would unlock background sync and native widgets&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔗 Try It Out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://spendsmart.gokulmithran.com/" rel="noopener noreferrer"&gt;spendsmart.gokulmithran.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this was helpful, drop a ❤️ and follow for more full-stack deep dives. Questions? Hit me in the comments!&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; &lt;code&gt;#webdev&lt;/code&gt; &lt;code&gt;#react&lt;/code&gt; &lt;code&gt;#java&lt;/code&gt; &lt;code&gt;#springboot&lt;/code&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>java</category>
      <category>springboot</category>
    </item>
  </channel>
</rss>
