Expense-tracker: LedgerFlow: The Expense Tracker That Solves the Boring Parts First
A look at the security plumbing, cross-origin glue, and defensive data model behind a full-stack finance app that behaves more like production software than a hobby project.
- LedgerFlow stands out because it treats browser cookies, CORS, and authentication as the core product problem, not as plumbing hidden behind the UI.
- Its backend favors continuity over strict rejection, which makes the app feel more like a transaction capture tool than an accounting system.
- The schema stays intentionally small, but the security and deployment choices make it read like production software.
- Against heavier finance tools, LedgerFlow is less complete but more revealing about how a real full-stack boundary is built.
Most expense trackers try to impress you with charts. LedgerFlow is more interesting because it starts with the part that usually breaks first: the boundary between a browser and an API. The repository’s real lesson is that a finance app becomes trustworthy only when cookies, CORS, auth, and deployment are designed as one system.
Why LedgerFlow is interesting before you even open the dashboard
LedgerFlow is a split-stack app built with React on the frontend and Django plus Django REST Framework on the backend. That sounds ordinary until you notice how much attention the project gives to the seams: JWTs in cookies, a custom authentication backend, custom CORS handling, and production-friendly static and database settings.
That is the real hook. This is a personal finance app that quietly behaves like a mini security case study. It is less about accounting features than about making a browser-based app survive the exact failures that usually appear only after deployment.
The real product is the handshake between browser and backend
# expenses/authenticate.py
class JWTCookieAuthentication:
def authenticate(self, request):
token = request.COOKIES.get('access_token')
if not token:
return None
return self.get_user_from_token(token), token
The choice to read the token from an HttpOnly cookie instead of localStorage changes the threat model. It reduces exposure to common XSS token theft patterns and makes authentication feel like part of the browser session rather than a blob the frontend manages itself.
That only works if the rest of the stack cooperates. LedgerFlow adds a custom CORS layer so credentialed requests can cross the frontend-backend split without the browser quietly dropping the session on the floor.
# expenses/cors_middleware.py
class BulletproofCorsMiddleware:
def process_request(self, request):
if request.method == 'OPTIONS':
response = HttpResponse()
response['Access-Control-Allow-Credentials'] = 'true'
return response
This is the sort of code you write only after a browser has already taught you a hard lesson. The middleware is not ornamental. It exists because the app is deployed across different origins, and cookies only behave if credentials and headers are aligned precisely.
How LedgerFlow avoids saying invalid input too early
LedgerFlow’s serializer logic is opinionated in a different way. Instead of treating missing titles or categories as hard failures, it backfills safe defaults like Untitled Transaction or a generic category so the user can keep moving.
That is a product decision, not just a coding trick. In a personal finance app, speed matters. Users often want to capture a transaction first and clean it up later.
# Simplified behavior from serializers.py
if not data.get('title'):
data['title'] = 'Untitled Transaction'
if not data.get('category'):
data['category'] = 'Food' if data.get('type') == 'EXPENSE' else 'Salary'
The trade-off is obvious. You gain fluidity, but you also accept that the backend is making judgment calls on behalf of the user. For this kind of app, that is a defensible choice.
A model that keeps finance simple without making it dumb
The schema is small on purpose. A single Expense model handles both income and spending through type choices, a Budget table enforces one budget per user and category, and UserProfile extends identity with verification state and OTP-related fields.
That combination keeps the core flows legible. There is no accounting maze hiding behind the scenes. The design is compact enough to support the app’s main behaviors without turning the database into a miniature enterprise suite.
| Model | What it does | Why it matters |
|---|---|---|
| Expense | Stores income and spending in one table | Keeps transaction logic simple and consistent |
| Budget | Locks one budget per user and category | Prevents conflicting category limits |
| UserProfile | Adds verification and OTP fields | Keeps auth extensions separate from core user data |
The interesting part is not novelty. It is restraint. LedgerFlow looks like a project that wants to stay small while still preserving the hooks needed for verification, reporting, and budget tracking.
Why the deployment choices matter more than they first appear
The deployment posture is practical: SQLite for development, PostgreSQL in production, dj_database_url for environment switching, and WhiteNoise for compressed static assets. The frontend lives separately from the backend, which is clean for team boundaries but immediately raises cookie and CORS complexity.
That split is the price of a modern app that is easy to host and easy to reason about. It is also why LedgerFlow is more interesting than a monolith hidden behind a single server. The architecture forces the developer to handle the browser boundary explicitly.
| Concern | LedgerFlow choice | Effect |
|---|---|---|
| Database | SQLite locally, PostgreSQL in production | Low-friction development and a real production datastore |
| Static files | WhiteNoise compressed manifest storage | Simple Django-friendly asset delivery |
| Frontend hosting | Vercel | Fast frontend deployment with a separate origin |
| Backend hosting | Render | Straightforward API hosting, but tighter CORS requirements |
That arrangement suits a solo project or small team well. It does, however, mean the repository spends real effort on problems that single-origin hobby apps never have to face.
How it stacks up against the self-hosted finance field
LedgerFlow is not trying to beat the category leaders at depth. It is smaller and less mature than heavyweights like Firefly III, less accounting-heavy than GnuCash, and less budgeting-centric than Actual Budget. That is not a weakness if your interest is the implementation pattern rather than ledger completeness.
| Project | Positioning | Strength | Where LedgerFlow differs |
|---|---|---|---|
| Firefly III | Full-featured self-hosted finance suite | Deep bookkeeping and rules | LedgerFlow is lighter and more app-like |
| Actual Budget | Clean budgeting-first tool | Strong UX for allocation | LedgerFlow focuses more on auth and boundary design |
| GnuCash | Classic accounting software | Accounting rigor | LedgerFlow is simpler and more web-native |
| LedgerFlow | Small full-stack finance app | Secure browser-to-API plumbing | It is most useful as a case study in seams, not breadth |
The editorial conclusion is straightforward. If you need mature finance management, there are stronger tools. If you want to study how a modest app can still take security, deployment, and user friction seriously, LedgerFlow is the more revealing repository.
What LedgerFlow gets right, and what still looks unfinished
The project is early and not pretending otherwise. The OTP flow appears partial, and the repo reads more like a thoughtful V1 than a finished accounting platform. But it already gets something important right: the boring parts are where trust is built.
That makes LedgerFlow worth studying. It shows how a humble expense tracker becomes genuinely interesting when the developer treats the browser boundary, the authentication layer, and the deployment split as first-class product problems.