Finper’s entire persistence layer is a single SQLite file managed by Drizzle ORM. There is no external database server to configure or maintain. The schema and the connection factory live in theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/soker90/finper/llms.txt
Use this file to discover all available pages before exploring further.
@soker90/finper-db workspace package, which is built and then imported by the API at runtime.
Drizzle ORM and SQLite
The@soker90/finper-db package exports a createDb(file) factory that opens a better-sqlite3 connection and returns a typed Drizzle database instance. The API creates this connection in src/db.ts:
server.ts calls Drizzle’s migrate() pointing at the packages/db/drizzle/ folder:
Multi-tenancy
Every table except
users contains a user column (type TEXT, references users.username). Every query in the repository layer filters by this column so that each user’s data is completely isolated. A user can never read or modify another user’s records.Example from the accounts schema:authMiddleware sets req.user = username (the string, not a numeric ID) and every controller passes it to the service, which passes it to the repository as the user filter.Entity overview
| Entity | Table(s) | Key Fields | Relations |
|---|---|---|---|
| User | users | id, username, password, isActive, createdAt | Root anchor for all other data |
| Account | accounts | id, name, bank, balance, isActive, user | Referenced by Transaction, Loan, Subscription, Supply |
| Category | categories | id, name, type, parentId, budgetRuleClass, user | Self-referential parent; referenced by Transaction, Budget, Loan |
| Transaction | transactions | id, date, categoryId, amount, type, accountId, note, storeId, subscriptionId, tags, user | Belongs to Account, Category, Store, Subscription |
| Store | stores | id, name, user | Referenced by Transaction |
| Budget | budgets | id, year, month, amount, categoryId, user | Unique index on (user, month, year, categoryId) |
| Debt | debts | id, from, date, amount, concept, type, user | Standalone |
| Goal | goals | id, name, targetAmount, currentAmount, deadline, color, icon, user | Standalone |
| Loan | loans | id, name, initialAmount, pendingAmount, interestRate, startDate, monthlyPayment, accountId, categoryId, user | Has many LoanPayment, LoanEvent |
| LoanPayment | loan_payments | id, loanId, date, amount, interest, principal, accumulatedPrincipal, pendingCapital, type, user | Belongs to Loan |
| LoanEvent | loan_events | id, loanId, date, newRate, newPayment, user | Belongs to Loan |
| Subscription | subscriptions | id, name, amount, currency, cycle, nextPaymentDate, categoryId, accountId, logoUrl, user | Referenced by Transaction |
| SubscriptionCandidate | subscription_candidates | id, transactionId, subscriptionIds, createdAt, user | Standalone candidate detection record |
| Stock | stocks | id, platform, ticker, name, shares, price, type, date, user | Standalone |
| Pension | pensions | id, date, employeeAmount, employeeUnits, companyAmount, companyUnits, value, user | Standalone |
| Property | properties | id, name, user | Has many Supply |
| Supply | supplies | id, name, type, propertyId, power/price fields, user | Belongs to Property; has many SupplyReading |
| SupplyReading | supply_readings | id, supplyId, startDate, endDate, amount, consumption fields, user | Belongs to Supply |
Schema conventions
Dates stored asNumber (Unix milliseconds)
All date/time columns use integer with Drizzle’s default numeric storage (Unix ms timestamp). The serializer layer converts these to JavaScript Date objects or ISO strings when building API responses.
Exceptions:
goals.deadline— stored as a plaininteger(may benullfor goals with no deadline).subscription_candidates.createdAt— stored as a plainintegerUnix ms timestamp.
REAL (IEEE 754 double)
Monetary values are stored as SQLite REAL (64-bit float). To prevent floating-point drift, any calculated amount must be rounded with the roundMoney helper before being written back to the database or sent to the client. Values read directly from the database without arithmetic do not need rounding.
text in SQLite and are always serialized as strings in API responses.
Enum values
Domain enumerations are stored as plain strings and defined as constants in packages/db/src/constants.ts:
| Constant | Values |
|---|---|
TRANSACTION | expense | income | not_computable |
LOAN_PAYMENT | ordinary | extraordinary |
SUPPLY_TYPE | electricity | water | gas | other |
buy | sell | dividend) and debt direction types (from | to) are documented in their respective schema files.
Backing up the database
The SQLite file path is controlled by theDATABASE_FILE environment variable (default: ./finper-dev.db). A backup is a simple file copy — no special tooling is required:
docker-compose.yml maps the database to /home/node/app/data/finper.db inside a named volume. Back up the file from the host volume mount point: