Architecture¶
Overview¶
Paisa is a personal finance application built around double-entry ledger files, with three execution modes:
- CLI mode for operations like
serve,update, andinit. - Web mode that serves a browser SPA and JSON API.
- Desktop mode (Wails) that embeds the same backend API in a native shell.
The core architecture is a modular monolith:
- A Go backend in
internal/for domain logic, data access, and API handlers. - A SvelteKit frontend in
src/that consumes backend APIs. - SQLite persistence managed through GORM.
- Ledger CLI integration for parsing and validating source journal files.
Runtime Entry Points¶
CLI entry¶
- Binary entrypoint:
paisa.go - Command composition:
cmd/root.go - Operational commands:
cmd/serve.gostarts HTTP server.cmd/update.gosyncs journal, prices, and portfolios.cmd/init.gocreates starter/demo data.
Web server entry¶
- HTTP router build and API registration:
internal/server/server.go - Listener startup:
internal/server/server.go(Listen) - Static asset embedding:
web/web.go
Desktop entry¶
- Wails bootstrap:
desktop/main.go - Desktop app lifecycle and DB open/migrate:
desktop/app.go - The desktop asset server uses the same backend router from
internal/server/server.go.
Layered Structure¶
Presentation Layer¶
- Frontend routes and UI:
src/routes/,src/lib/ - API calls and auth token handling:
src/lib/utils.ts - App-level state stores:
src/store.ts
API Layer¶
- Gin router and middleware:
internal/server/server.go - Domain handlers grouped by concern:
- Dashboard, cash flow, budget, net worth, income/expense.
- Ledger editor and sheet editor.
- Portfolio/prices/goals/credit cards.
Domain and Service Layer¶
- Accounting primitives and account views:
internal/accounting/ - Capital gains, market price and other business workflows:
internal/service/ - Taxation and forecasting logic:
internal/taxation/,internal/prediction/,internal/xirr/
Data Access Layer¶
- DB open and helper utilities:
internal/utils/utils.go - Models and sync orchestration:
internal/model/model.goand relatedinternal/model/* - Query helpers:
internal/query/
Integration Layer¶
- Ledger engine wrapper:
internal/ledger/ - External price/portfolio sources:
internal/scraper/
High-Level Data Flow¶
- User action in frontend route triggers API call via
ajaxinsrc/lib/utils.ts. - Request enters Gin router in
internal/server/server.go. - Auth middleware validates optional
X-Authtoken for/api/*when users are configured. - Handler delegates to accounting/service/model/query modules.
- Data is loaded from SQLite and/or refreshed via ledger/scraper flows.
- JSON response is sent to frontend and rendered by Svelte components.
Key Architectural Characteristics¶
Strengths¶
- Shared backend between web and desktop keeps behavior consistent.
- Clear package boundaries in
internal/by functional domain. - Config schema validation in
internal/config/reduces malformed config risk. - Embed-based static serving simplifies deployment to a single binary.
Current Constraints¶
- Global mutable config singleton (
internal/config/config.go) creates tight coupling. - HTTP handlers contain orchestration logic and domain calls in a single layer.
- GORM
AutoMigrate-driven schema evolution limits explicit migration control. - Auth is designed for single-user/self-hosted deployments, not multi-tenant SaaS.
Primary Deployment Surfaces¶
- Native binary (
paisa serve), typically reverse-proxied in production. - Docker images (
Dockerfile,Dockerfile.hledger,Dockerfile.beancount,Dockerfile.all). - Desktop artifacts from Wails in
desktop/build/. - Static/documentation site via MkDocs (
mkdocs.yml,docs/).
Suggested Next Architectural Evolutions¶
- Introduce explicit service interfaces for core domains (sync, reporting, editor).
- Add DB migration versioning rather than relying only on implicit auto-migrations.
- Move mutable write-protection checks to middleware/policy layer.
- Add structured observability primitives (request IDs, metrics, trace correlation).