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.

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.
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.
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.
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.