๐๏ธ System Architecture Overview
LibreFolio is designed as a modern web application with a clear separation between the backend API and the frontend user interface.
๐บ๏ธ High-Level Diagram
The architecture can be visualized as three main components:
graph TD
subgraph User's Browser
Frontend[SvelteKit Frontend]
end
subgraph Server
Backend[FastAPI Backend]
Database[(SQLite Database)]
end
subgraph External Services
direction TB
BrokerAPIs[Broker APIs / CSVs]
PricingAPIs[Pricing APIs]
FXAPIs[FX Rate APIs]
WebSearch[Web Search Engine<br/>ddgs metasearch]
end
subgraph Plugins
direction TB
B_Plugins[BRIM Plugins]
A_Plugins[Asset Plugins]
F_Plugins[FX Plugins]
R_Plugins[Risk Analytics]
T_Plugins[Tool Plugins]
end
Frontend -- HTTP API Calls --> Backend
Backend -- SQLAlchemy ORM --> Database
Backend -- Uses --> Plugins
B_Plugins -- Parses --> BrokerAPIs
A_Plugins -- Fetches --> PricingAPIs
F_Plugins -- Fetches --> FXAPIs
A_Plugins -. Last-resort URL resolve .-> WebSearch
๐งฑ Components
-
๐ฅ๏ธ Frontend (SvelteKit): A single-page application (SPA) that runs in the user's browser. It communicates with the backend via a RESTful API to fetch and display data.
-
โ๏ธ Backend (FastAPI): A Python-based API server that handles all business logic, including:
- ๐ User authentication and authorization.
- ๐๏ธ Database operations (CRUD).
- ๐ฅ Data import from brokers (BRIM).
- ๐ Fetching asset prices and FX rates from external sources.
-
๐๏ธ Database (SQLite): A single-file database that stores all user data, including transactions, assets, user settings, and cached data.
-
๐ Provider Plugins: A system of pluggable modules that abstract the interaction with external data sources. This makes it easy to add support for new brokers, pricing APIs, or FX rate providers without modifying the core application logic. The same registry mechanism also discovers plugins that call no external service: the risk analytics and the Tools.
๐ Key Subsystems
For detailed architectural documentation of specific subsystems, see:
- ๐๏ธ Database Schema: Data models and relationships.
- ๐ค Users & Brokers: Authentication and multi-user access control.
- ๐ Access Control (RBAC): Role-based broker access (Owner/Editor/Viewer).
- โ๏ธ Settings System: User preferences and global settings.
- ๐ฅ BRIM Architecture: Broker Report Import Manager.
- ๐ Asset Pricing: Asset data fetching and metadata.
- ๐ See also: Asset Search & Link-Finder for the three-layer interactive search (on-site โ
ddgsweb link-finder โresolve_url); best-effort, last-resort, and never used on automated price fetches.
- ๐ See also: Asset Search & Link-Finder for the three-layer interactive search (on-site โ
- ๐ฑ FX Architecture: Foreign Exchange system.
- ๐ See also: FX Configuration & Routing for multi-provider setup.
- ๐งฎ Financial Math Layer:
backend/app/services/financial_math/, the one home of financial calculations โ package map, layer rules, guards. - ๐ Risk Engine: Bulk risk analytics over a portfolio, a broker, an asset or a selection of assets โ plugin catalogue, eligibility verdicts, and simulation and optimization in spawned worker pools.
- ๐งฐ Tool Plugins: Atomic, typed calculations behind a versioned catalogue (first tool: the PAC allocator), each computed in a spawned process the executor owns.
- ๐งญ Onboarding Guides: Welcome setup, intro tour and contextual guides, with versioned per-user progress kept by the backend.
- ๐ File Upload System: Custom uploads stored with a JSON metadata sidecar, and the default avatars seeded at startup (
backend/app/services/static_uploads.py); image previews go through an in-memory cache (PreviewCacheinbackend/app/api/v1/uploads.py: 50 MB by default,PREVIEW_CACHE_MAX_MB, entries expire after 1 hour). Broker report files belong to BRIM. Where all these files live: Data Directory on Disk.
๐ Request Flow Example: Displaying Portfolio
- User logs in and navigates to the dashboard.
- The Frontend makes an API request to
GET /api/v1/portfolio. - The Backend receives the request, authenticates the user, and queries the Database for the user's transactions.
- For each asset, the backend may need to fetch the latest price. It calls the appropriate Asset Provider Plugin (e.g., Yahoo Finance).
- If currency conversion is needed, the backend calls the FX Provider Plugin to get the latest exchange rate.
- The backend processes the data, calculates portfolio metrics, and returns a JSON response to the frontend.
- The Frontend receives the JSON data and renders the portfolio dashboard.
๐ Server Startup
The FastAPI lifespan (lifespan() in backend/app/main.py) prepares everything before the first request, in this order:
- Logs
Starting LibreFoliowith the version, the database path and the test mode. - Checks the signal-plugin runtime (
validate_signal_runtime()) and discovers the signal plugins. - Loads the Tool catalogue (
ToolPluginRegistry.get_snapshot(), in a thread). - Creates the data directories (
ensure_data_dirs()). - Loads the risk scenario catalogue (
initialize_risk_scenario_catalog()). - Seeds the default avatars (
seed_default_avatars()). - Creates or migrates the database (
ensure_database_exists()); a failedalembic upgrade headstops the server (sys.exit(1)). - Runs the post-migration fixes, which never stop the startup.
- Seeds the missing global settings (
_initialize_global_settings()). - Starts, in the background, the provider cache pre-warm (
_prewarm_provider_caches()) and the market-holiday table, built in its own process (start_market_holiday_prewarm()). - Starts the scheduler task (
scheduler_loop()).
On shutdown it stops the Tool executor and the scheduler, then shuts down the asset, FX and BRIM providers, the BRIM parse pool, the quant worker pools and any market-holiday build, and closes the TTL caches.
๐ณ Docker Image
The production image is runtime-only: the frontend (SvelteKit) and the documentation (MkDocs) are built on the host and copied in, so the image carries neither Node.js nor the documentation toolchain. ./dev.py docker build prepares those builds first (see Developer Workflow โ Docker Integration); the release workflow builds and tags the published images (see Release & CI/CD Pipeline).
graph LR
subgraph "Host (build)"
FE["frontend/src"]
MK["mkdocs_src/"]
BE["backend/"]
PF["Pipfile*"]
end
subgraph "Docker Image (runtime)"
FB["frontend/build/"]
MS["mkdocs_src/site/"]
BC["backend/"]
PP["Python packages"]
end
FE -- "npm run build" --> FB
MK -- "mkdocs build" --> MS
BE -- "copy" --> BC
PF -- "pipenv requirements" --> PP
- Build stages (
Dockerfile):docs-fullanddocs-lighttake the pre-builtmkdocs_src/site/, anddocs-lightdeletes the gallery images before they reach the final image (theDOCS_VARIANTbuild argument picks one).frontendrunsscripts/docker/check_frontend_build.sh, which refuses a debug, coverage-instrumented or sourcemappedfrontend/build/.pybuilderinstallsrequirements.txtwith the compiler toolchain, and only the installed packages reach the finalpython:3.13-slimstage, which addsgosuandsqlite3. - Contents:
backend/,scripts/,dev.py, the checked frontend build, the documentation site,VERSION(the git version frozen at build time, since the image has no.git/) and.env.examplecopied as/app/.env..dockerignorekeeps out, among others,backend/data/,backend/test_scripts/, the documentation sources and the developer journal.dev.pyregisters a command group only when the directories it needs exist, so in the imagetest,i18n,mkdocs translateandmkdocs translate-validatestay listed but answer not available in this installation and exit with2(see Test Mode in the Container). - Startup:
entrypoint.shstarts as root, creates the data directory and hands it to the numericLIBREFOLIO_UID:LIBREFOLIO_GID(theUID/GIDbuild arguments, default1000:1000), thenexec gosureplaces itself with the command. The default command isuvicorn backend.app.main:app --host 0.0.0.0 --port 6040: one worker, PID 1 of the container, always on port6040.PORTonly changes the host side of the Compose port mapping, and the image'sHEALTHCHECKprobes/api/v1/system/healthon6040too. - Database: the container runs the same startup sequence as any server, so pending migrations and the post-migration fixes are applied before the first request: a container never needs a manual
db upgrade.
๐งฐ Tech Stack
โ๏ธ Backend
- ๐ FastAPI: A high-performance web framework for building APIs with Python (LibreFolio requires Python 3.13), based on standard Python type hints. It provides automatic interactive documentation (Swagger UI and ReDoc).
- ๐๏ธ SQLAlchemy: The SQL toolkit and Object-Relational Mapper (ORM) used for all database interactions. LibreFolio uses SQLAlchemy's asyncio support for non-blocking database queries.
- ๐ Pydantic: A data validation and settings management library. Used extensively for defining data schemas, validating API requests, and managing application settings.
- ๐ Alembic: A lightweight database migration tool for SQLAlchemy. See Database Migrations.
- ๐๏ธ SQLite: The default database engine. Simple, serverless, and perfect for a self-hosted application. Configured in WAL (Write-Ahead Logging) mode for better concurrency.
๐จ Frontend
- ๐ฅ๏ธ SvelteKit: A web application framework for building fast, modern user interfaces with server-side rendering, routing, and great developer experience.
- ๐ TypeScript: A statically typed superset of JavaScript that adds type safety to the frontend codebase.
- ๐จ TailwindCSS: A utility-first CSS framework for rapidly building custom designs.
- โก Vite: The build tool and development server. Provides extremely fast Hot Module Replacement (HMR).
๐งช Testing
- ๐ Pytest: The framework used for writing and running backend tests.
- ๐ญ Playwright: A framework for end-to-end testing of the web application.
- ๐ Coverage.py: A tool for measuring code coverage of Python programs.