Back to Projects

MySpendo

Personal Finance Tracker

MySpendo Dashboard

Overview

A personal finance tracker I built to learn Go. Users can track income and expenses, set budgets with alerts, and automate recurring transactions like rent and subscriptions.

I wanted to learn Go backend development and work with real complexity like scheduled jobs and handling date edge cases. Turned out to be more interesting than I expected, especially the recurring transaction logic.

25+
REST endpoints
Hourly
Background job
3
Tech Stack Layers

Architecture

MySpendo Architecture Diagram

The app has three parts: Next.js frontend on Vercel, Go backend on Oracle Cloud, and PostgreSQL database on Neon.

Frontend talks to backend through REST APIs. Backend handles authentication with JWT, business logic, and database operations. An hourly job runs in the background to process recurring transactions.

I used PostgreSQL advisory locks to make sure the background job doesn't create duplicate transactions if multiple instances run. Connection pooling keeps things fast with 25 max connections.

Data flow is simple: user creates a transaction in the UI, frontend sends it to the backend, backend saves it to PostgreSQL and checks if any budgets are exceeded, then returns the response.

Tech Stack

Backend

GoGorilla MuxJWT authPostgreSQL driver

Database

Neon PostgreSQLAdvisory locksConnection pooling

Frontend

Next.js 15TypeScriptTailwind CSSRecharts

Deployment

Oracle Cloud (1GB RAM)Vercel

Technical Challenges

PostgreSQL Advisory Locks

Problem

Hourly background job processes recurring transactions. If two instances run at the same time, users get duplicate transactions.

Solution

Used PostgreSQL advisory locks. Job tries to get lock before running. If it cannot get lock, another instance is already running, so it exits. Releases lock when done.

Impact

No duplicate transactions. Job runs reliably every hour even during server restarts.

Recurring Transaction Edge Cases

Problem

Users schedule recurring rent on the 31st, but February only has 28 days. Leap year dates break in non-leap years.

Solution

For month-end dates, use the last day of the target month. Feb 29 becomes Feb 28 in non-leap years. Added max iteration limit to prevent infinite loops if logic breaks.

Impact

Recurring transactions work for all date scenarios. No bugs reported.

Key Features

Transaction tracking (income and expenses)
Budget alerts when spending exceeds limits
Automated recurring transactions
Analytics with charts
CSV/JSON export
Category-based organization
Date range filtering

What I Learned

Go Backend: First time building backend with Go. Error handling is verbose but forces you to think about edge cases. Compiled binary makes deployment simple.

PostgreSQL Advisory Locks: Never used these before. Perfect for preventing duplicate jobs without building a separate queue system.

Date Logic is Harder Than Expected: Month-end dates, leap years, and timezones all create edge cases. Spent way more time on date logic than I thought I would.

Deployment on Free Tier: Deployed on Oracle Cloud's 1GB RAM instance. Had to optimize connection pooling and use prepared statements to keep memory usage low. Constraints force better optimization.

What I'd Do Differently: Would use a proper job queue like BullMQ instead of cron. Add pagination from day one instead of retrofitting it. More tests upfront instead of adding them later.