Lessons Learned Building a Microservices Expense Tracker
Source: Dev.to
Introduction
Have you ever taken a small idea and deliberately made it more complex, not to overengineer it, but to understand how real systems actually work?
That’s exactly what I did when I built an Expense Tracker. Instead of going with a single monolithic backend, I chose to split it into multiple microservices that communicate with each other through APIs. What started as a curiosity‑driven experiment quickly turned into a practical lesson in system design, data flow, authentication, and thinking beyond individual endpoints.
This wasn’t about building something “production‑ready.” It was about learning how backend systems behave when responsibilities are separated.
Why Microservices for a Small App?
Most small applications are monolithic by default. Everything lives together, shares the same context, and grows tightly coupled over time. I intentionally avoided that.
I wanted to understand:
- How services talk to each other
- How authentication works across multiple services
- How breaking responsibilities changes the way you think about backend design
Microservices forced me to stop thinking in terms of “routes” and start thinking in terms of systems. Even at a small scale, that shift was valuable.
How the System Is Structured
The backend is split into four independent services, each with a clear role:
Auth
Handles user registration, login, and JWT generation. This service is responsible for identity and trust.
Expense
Manages all expense‑related operations such as creating, updating, and fetching expense records.
Analytics
Focuses on generating summaries and insights like spending trends and category breakdowns by using expense data.
Gateway
Acts as the single entry point for the frontend. Every request passes through it before being routed to the appropriate service.
The frontend never talks to services directly. All requests go through the Gateway, JWTs are validated, and only then is the request forwarded. Analytics consumes expense data but doesn’t store its own copy, keeping the system simple and modular.
Lessons I Learned Along the Way
- Modularity changes how you think – Separating responsibilities immediately brought clarity. Each service had a purpose, reducing mental overhead when working on features or fixing bugs.
- Service communication needs intention – Even simple REST calls require planning. I had to consider request flow, failure cases, and how one service depends on another without tightly coupling them.
- Authentication is more than login logic – JWT‑based authentication across services showed that authentication is about trust propagation, not just verifying credentials once.
- Systems thinking matters – I shifted from focusing only on functions and endpoints to considering data flow, service boundaries, and the impact of changes across the whole system.
- Data handling shapes everything – Analytics depends entirely on how expense data is structured. I learned to think ahead about querying, aggregation, and performance—even for a small dataset.
- Debugging across services is a real skill – Tracing a request across multiple services taught me to debug methodically and view the system as a connected whole rather than isolated code files.
Final Thoughts
This project wasn’t about building a perfect application. It was about experimenting, learning, and growing.
Building with microservices showed me how modular design, secure communication, and data‑driven features can coexist—even in a simple app. More importantly, it helped me build confidence in reasoning about backend systems, not just writing code.
By the end, I wasn’t only tracking expenses—I was tracking my own growth as a developer and preparing myself to handle more complex architectures in the future.