DEV Community

Cover image for Track My Cash: Building a Secure, Decoupled Ledger with Spring Boot, Vanilla JS, and Gemini
Rohith V
Rohith V

Posted on

Track My Cash: Building a Secure, Decoupled Ledger with Spring Boot, Vanilla JS, and Gemini

Built with Google Gemini: Writing Challenge

This is a submission for the Built with Google Gemini: Writing Challenge

Reusing the side project that I had worked on recently for this Challenge

What I Built with Google Gemini

I built BookKeeping, a full-stack financial ledger application designed to solve a very specific problem: keeping track of money I lend to friends without needing a bloated, complex accounting tool.

The application is a stateless, decoupled Single Page Application (SPA). The frontend is built entirely in Vanilla JavaScript, HTML, and CSS (zero framework dependencies!) and now deployed securely on Google Cloud Run. It communicates with a robust backend powered by Java, Spring Boot 3, Spring Security 6, and a PostgreSQL database hosted on Render.

Google Gemini was my pair-programming partner throughout the entire development cycle. Gemini played a massive role in helping me stitch together the tricky parts of a decoupled architecture. Specifically, we used Gemini to:

  1. Architect the Security Flow: It helped me implement stateless JWT authentication using HttpOnly cookies and standard Bearer tokens.

  2. Handle Browser Security Hurdles: Gemini and I worked through aggressive third-party cookie blocking (like Safari ITP) by engineering a strict CORS and JSON-payload strategy to neutralise CSRF attacks natively.

  3. Build the Math Engine: We built a custom "Partial Repayment" algorithm together that dynamically decreases loan amounts and automatically triggers database row deletion when a balance hits zero.

  4. Construct Cloud Run Docker Environments: When migrating the frontend to Google Cloud Run, we built a lean Alpine Nginx container. Gemini instantly flagged that Cloud Run overrides standard port bindings—so we implemented a dynamic nginx.conf.template that natively injects Cloud Run's $PORT environment variable during deployment.

  5. Design Native Operating System Localisation: During my Multi-Currency expansion logic, my browser stubbornly refused to format Euros properly. Gemini analysed the frontend code and detected a hardcoded 'en-US' locale string overriding the dynamic ISO codes. By removing the strict binding and passing undefined into the JavaScript Intl.NumberFormat API, my interface immediately inherited my computer's dynamic OS localisation formatting.

Note on Authentication: You might notice the login system is intentionally very straightforward. Since this application is built to solve a highly focused, personal task (tracking our own money), a complex OAuth or multi-role hierarchy wasn't necessary. The simple stateless JWT login keeps the app lightweight and entirely under control.

Demo

Github Link : https://rohithv07.github.io/BookKeeping/

Cloud Run URL Embedded :

GitHub logo Rohithv07 / BookKeeping

Record the debts that we are lending to other people.

BookKeeping

Record the debts that we are lending to other people.

What was implemented

  1. Spring Boot App: A robust REST API running on Java 21, built using Gradle.
  2. Database Models
    • Borrower: Saves name, email, and phone.
    • Loan: Tracks amount, date lent, exact 1-month (dueDate), and loan status (ACTIVE vs REPAID).
  3. Core API Endpoints
    • POST /api/borrowers and GET /api/borrowers
    • POST /api/loans and GET /api/loans
    • PUT /api/loans/{id}/repay
  4. Notification Job: The NotificationService is scheduled to run every day at 8:00 AM. It queries the database for any active loans where the due date is today or earlier and dispatches an email exclusively to your personal Gmail account (as requested).
  5. System Architecture Upgrades
    • Interface-Driven Design: The Service layer employs interface contracts (BorrowerService, LoanService) for loose coupling and scalability.
    • Data Transfer Objects (DTOs): API payloads exclusively use BorrowerDto and…

What I Learned

Building this application taught me that security and distributed environments are not magic.

When you separate your frontend (Google Cloud Run) from your backend (Render), security suddenly becomes your biggest technical hurdle. Attempting to pass cookies across domains taught me deep, unexpected lessons about modern browser architecture, Preflight OPTIONS requests, and why major browsers actually intercept cross-domain cookies to protect privacy.

Additionally, I learned incredibly valuable lessons about Multi-Tenancy database migrations. When I upgraded the application to securely map existing financial records to authenticated user profiles, all of my old data completely "vanished"! By collaborating with Gemini, we built an automated DataMigrationRunner in Java that essentially scanned the PostgreSQL database for legacy, orphaned data during startup and flawlessly bound those records back to my isolated user profile using newly minted foreign keys.

Working with Gemini also taught me the value of interactive debugging. Instead of endlessly combing through StackOverflow threads when my frontend deployment returned an obscure gcloud run deploy port binding error, I could simply paste the logs directly to Gemini. It structurally unpacked how Nginx Alpine images process templates and provided the exact variable substitution fix instantaneously.

Google Gemini Feedback

What worked well: Gemini is incredibly strong at context retention across complex files. When I needed to refactor my Spring Security JwtAuthenticationFilter to accept both cookies and Bearer tokens, Gemini didn't just give me a generic code snippet; it understood the exact architecture of my Java files and provided surgical, drop-in code replacements. The same deep architectural awareness proved vital during our PostgreSQL schema migrations and Cloud Run Nginx optimisations.

Where I ran into friction: There were a few moments where Gemini would try to eagerly fix a bug before I had finished explaining the complete symptom. For example, when my frontend JavaScript was misfiring an onclick event, it occasionally suggested backend fixes before realising the bug was actually a simple string interpolation error in my Vanilla JS HTML template. Once we clarified the scope, however, the eventual fix was spot on. Additionally, I experienced a recurring loop where the agent would continuously execute superficial internal baseline scripts (like echo "do something") just to bypass strict system task-boundary protocols before finally delivering a direct chat response to my question!

Top comments (0)