Files
SimpleFinanceDash/.planning/codebase/ARCHITECTURE.md

8.9 KiB

Architecture

Analysis Date: 2026-03-11

Pattern Overview

Overall: Monolithic full-stack with clear backend (Go) and frontend (React) separation, communicating via REST API. The Go binary embeds the compiled React frontend and serves the complete SPA with static assets and API routes.

Key Characteristics:

  • Monolithic deployment (single Docker container)
  • Backend-for-Frontend pattern: Go handles routing, SPA serving, and REST API
  • Session-based authentication via HTTP-only cookies and JWT tokens
  • Server-side budget totals computation (not stored in DB)
  • Frontend client-side routing within embedded SPA
  • User-scoped data isolation at database level

Layers

Presentation (Frontend):

  • Purpose: Render UI, handle user interactions, manage form state and navigation
  • Location: frontend/src/
  • Contains: React components, pages, hooks, translation files, styling (Tailwind)
  • Depends on: REST API via lib/api.ts, i18n for localization, React Router for navigation
  • Used by: Users accessing the application through the browser

API/Handlers (Backend):

  • Purpose: HTTP request routing, validation, and response serialization
  • Location: backend/internal/api/
  • Contains: router.go (chi mux setup, middleware, routes), handlers.go (HTTP handler functions)
  • Depends on: Database queries, authentication, models
  • Used by: Frontend making API calls, middleware chain

Authentication (Backend):

  • Purpose: Credential verification, session token generation/validation, user context injection
  • Location: backend/internal/auth/
  • Contains: Password hashing (bcrypt), JWT token generation/validation, session cookies, auth middleware
  • Depends on: Models (User), external crypto libraries
  • Used by: API handlers, router middleware

Database Layer (Backend):

  • Purpose: Persist and retrieve user data (users, categories, budgets, budget items)
  • Location: backend/internal/db/
  • Contains: db.go (connection pool, migrations runner), queries.go (CRUD operations)
  • Depends on: PostgreSQL 16, pgx driver, models
  • Used by: API handlers, migration system

Models (Backend):

  • Purpose: Domain type definitions shared between API and database layers
  • Location: backend/internal/models/
  • Contains: User, Category, Budget, BudgetItem, BudgetTotals, BudgetDetail structs with JSON tags
  • Depends on: External types (uuid.UUID, decimal.Decimal)
  • Used by: API handlers, database queries, JSON serialization

Frontend Hooks:

  • Purpose: Encapsulate API data fetching and state management
  • Location: frontend/src/hooks/
  • Contains: useAuth.ts (login, register, logout, user state), useBudgets.ts (budget list, selection, loading state)
  • Depends on: React hooks, API client
  • Used by: Page components and nested component trees

Frontend API Client:

  • Purpose: Centralize HTTP requests with common configuration, error handling, type definitions
  • Location: frontend/src/lib/api.ts
  • Contains: Type definitions (User, Category, Budget, BudgetItem, BudgetDetail), request helper, namespace functions for routes (auth, categories, budgets, budgetItems, settings)
  • Depends on: Fetch API, error handling
  • Used by: All hooks and components making API calls

Data Flow

User Registration/Login Flow:

  1. User submits credentials via LoginPage or RegisterPage
  2. Frontend calls auth.register() or auth.login() from lib/api.ts
  3. API request goes to /api/auth/register or /api/auth/login
  4. Backend handler (handlers.go) validates credentials, hashes password (bcrypt)
  5. Database layer creates user or validates stored password
  6. Backend generates JWT token via auth.GenerateToken() and sets HTTP-only session cookie
  7. Frontend receives User object, stores in useAuth() state
  8. Frontend redirects to authenticated view (AppLayout with Router)

Budget Data Flow:

  1. DashboardPage or BudgetSetup component loads via page render
  2. useBudgets hook calls budgets.list() on mount
  3. API request goes to /api/budgets
  4. Auth middleware validates session cookie and injects userID into context
  5. Handler retrieves budgets for authenticated user via queries.ListBudgets(userID)
  6. Database joins budget items with categories and computes totals server-side
  7. Backend responds with Budget list (or BudgetDetail with Items and Totals on /api/budgets/{id})
  8. Frontend receives data, updates component state
  9. Components render UI using fetched data

State Management:

  • Authentication: Centralized in useAuth() hook; persists in React state during session, restored from /api/auth/me on page load
  • Budget data: Managed per-page/component via useBudgets() hook; re-fetched when creating/updating/deleting
  • Form state: Local component state (useState) for forms like BudgetSetup, VariableExpenses
  • i18n: Global i18n instance in frontend/src/i18n/index.ts synced with user preference from backend

Key Abstractions

JWT Session Token:

  • Purpose: Stateless authentication without server-side session storage
  • Examples: Generated in backend/internal/auth/auth.go via GenerateToken(), validated in Middleware() and Me() handler
  • Pattern: Token contains user ID, expires in 7 days; stored in HTTP-only cookie session

User-Scoped Data:

  • Purpose: All queries filter by authenticated user ID to prevent cross-user data leaks
  • Examples: Categories, Budgets, BudgetItems all require userID in queries (queries.ListCategories(userID), queries.ListBudgets(userID))
  • Pattern: userID injected into request context by auth middleware, extracted via auth.UserIDFromContext()

Category Types:

  • Purpose: Categorize budget items (bill, variable_expense, debt, saving, investment, income)
  • Examples: In backend/internal/models/models.go as enum, mirrored in frontend API types, mapped to category_type ENUM in PostgreSQL
  • Pattern: Controls how budget totals are computed and displayed (separate rows for each type)

BudgetDetail with Computed Totals:

  • Purpose: Single response containing budget, items, and pre-computed category totals
  • Examples: Returned by GetBudgetWithItems(), includes BudgetTotals with budget/actual for each category type
  • Pattern: Server-side computation reduces client logic; frontend only displays values

API Client Namespaces:

  • Purpose: Group related endpoints for discoverability and organization
  • Examples: auth.*, categories.*, budgets.*, budgetItems.*, settings.* in frontend/src/lib/api.ts
  • Pattern: Declarative CRUD-style methods (list, create, get, update, delete) mapped to HTTP verbs and paths

Entry Points

Backend:

  • Location: backend/cmd/server/main.go
  • Triggers: go run ./cmd/server or binary execution in Docker
  • Responsibilities: Reads environment variables, connects to PostgreSQL, runs migrations, initializes API router with embedded frontend, starts HTTP server with graceful shutdown

Frontend:

  • Location: frontend/src/main.tsx
  • Triggers: Vite dev server or loaded index.html in production
  • Responsibilities: Mounts React app into DOM root element, applies StrictMode for dev warnings

Frontend App Root:

  • Location: frontend/src/App.tsx
  • Triggers: Mounted by main.tsx
  • Responsibilities: Initializes BrowserRouter, manages auth state via useAuth(), conditionally renders LoginPage/RegisterPage or authenticated app with Routes

Error Handling

Strategy: RESTful status codes with JSON error messages. Frontend catches API errors and displays user-friendly messages.

Patterns:

  • Backend HTTP errors: writeError() helper sets HTTP status (400, 401, 404, 409, 500) and JSON body {"error": "message"}
  • Frontend API errors: ApiError class wraps HTTP status and message; caught in try/catch blocks
  • Auth failures: Return 401 Unauthorized with "unauthorized" message; frontend redirects to login
  • Validation failures: Return 400 Bad Request with specific field or format error messages
  • Unique constraint violations: Return 409 Conflict for duplicate email, duplicate OIDC subject
  • Not found: Return 404 for missing resources (budget, category, user)
  • Server errors: Return 500 for unexpected failures (password hashing, database errors)

Cross-Cutting Concerns

Logging: Backend uses Go's log package; logs connection errors, migrations, server startup/shutdown. Frontend uses console (no dedicated logger).

Validation: Backend validates on handler entry (required fields, date formats). Frontend validates form state (required inputs, date picker constraints). No shared validation schema.

Authentication: Session cookie with JWT payload. Middleware on protected routes extracts userID from context. Frontend stores user in state and refetches on navigation.

Internationalization: Frontend uses i18n-next with translation files in frontend/src/i18n/ (de.json, en.json). User preference stored in database (preferred_locale), synced via useAuth on login.

CORS: Enabled for localhost:5173 (Vite dev) and localhost:8080 (Docker production). Credentials included in requests.


Architecture analysis: 2026-03-11