MEHDI.
RETURN_TO_INDEX

Building a Privacy-First Self-Hosted Finance Manager

6 min read
#React#TypeScript#MySQL#Node.js#Prisma#Security#Self-Hosting

Every personal finance application I tried had the same problem: my financial data lived on someone else's server. Budget categories, transaction histories, account balances, recurring payments -- all of it stored in a cloud I did not control, governed by a privacy policy I did not read.

DepanceAPP is my answer to that problem: a self-hosted personal finance manager that keeps all financial data on infrastructure the user controls. It is an open-source application built with React, TypeScript, Node.js, Express, Prisma and MySQL (MariaDB), designed to run in a Docker container on a home server or a VPS.

Why Self-Host a Finance Application

The motivation is not paranoia. It is about understanding where sensitive data lives and who has access to it. Financial data is among the most personal information a person generates: income, spending habits, debts, savings patterns. Storing that data on a third-party service means trusting their security practices, their employee access controls, and their business model.

Self-hosting does not make the data invulnerable. It shifts the responsibility. The user becomes responsible for backups, security updates, and access control. That is a real tradeoff, and DepanceAPP is designed for people who are willing to make it.

Core Features

The application covers the fundamentals of personal finance management:

Accounts and Transactions

Users can create multiple accounts (checking, savings, credit cards, cash) and record transactions between them. Each transaction has an amount, date, category, description, and optional tags. Transfers between accounts are handled as linked transactions to maintain balance consistency.

Income and Expenses

Transactions are classified as income or expenses. The dashboard shows a summary of both for any given period, with breakdowns by category. This gives a clear picture of where money comes from and where it goes.

Categories and Tags

Categories provide structure (groceries, rent, entertainment, transportation). Tags provide flexibility (vacation, tax-deductible, shared-expense). A single transaction can have one category and multiple tags, which allows for cross-cutting queries like "all tax-deductible expenses across all categories."

Monthly Budgets

Users can set monthly spending limits per category. The application tracks actual spending against these budgets and shows progress indicators. When a category approaches its limit, the visual feedback makes it obvious.

Recurring Transactions

Rent, subscriptions, salary deposits, and other predictable transactions can be set up as recurring entries. The application creates them automatically on the specified schedule, reducing manual data entry for routine items.

Multi-Currency Support

For users who operate in multiple currencies, accounts can be configured with different base currencies. Transactions are recorded in the account's currency, and the dashboard can display totals in a preferred currency.

Dashboards and Charts

The main dashboard provides interactive charts showing spending trends, category breakdowns, budget progress, and net worth over time. These visualizations help users understand patterns that are hard to see in a transaction list.

Authentication and Security

The authentication system uses JWT with access tokens and refresh tokens. Access tokens are short-lived (15 minutes by default), and refresh tokens allow obtaining new access tokens without re-entering credentials. Refresh tokens can be revoked, which invalidates the session.

To protect against brute-force login attempts, the system implements account lockout after a configurable number of failed attempts. Each login attempt (successful or not) is recorded with timestamp, IP address and user agent.

// Simplified login flow
const MAX_ATTEMPTS = 5;
const LOCKOUT_DURATION = 15 * 60 * 1000; // 15 minutes

async function login(email: string, password: string) {
  const attempts = await getRecentAttempts(email);

  if (attempts.failed >= MAX_ATTEMPTS) {
    const lastAttempt = attempts.lastAttemptAt;
    if (Date.now() - lastAttempt.getTime() < LOCKOUT_DURATION) {
      throw new Error("Account temporarily locked.");
    }
  }

  // ... verify credentials
}

Audit Logging

Every significant action creates an audit log entry: login, logout, password change, account creation, transaction modification, and settings change. Each entry records the action, the user, the timestamp, and relevant context. The audit log is append-only and cannot be modified through the application.

Docker Deployment

The application is designed to run in Docker with a single docker-compose.yml. The container runs the Node.js application, and the MySQL (MariaDB) database runs in a separate container or uses an external instance.

Volumes are critical for data persistence. The database data directory is mounted as a volume, ensuring that data survives container rebuilds. The application does not store any data in the container's filesystem outside of the database.

Backups are the user's responsibility. The simplest approach is to schedule regular mysqldump exports to a separate location. The application does not include a built-in backup system because that would couple the backup strategy to the application, when it should be coupled to the infrastructure.

The Tradeoffs of Self-Hosting

Self-hosting a finance application creates responsibilities that a cloud service handles transparently:

Backups: If the server's disk fails and there are no backups, the data is gone. A cloud service typically has redundancy built in.

Availability: A home server might go down during a power outage. A cloud service has uptime guarantees (and the infrastructure to back them up).

Security updates: The user must keep the operating system, Docker, and the application itself updated. A cloud service handles this behind the scenes.

Access from anywhere: A self-hosted application on a home network is not accessible from outside without additional configuration (VPN, reverse proxy, or port forwarding). Each approach adds complexity and potential security surface area.

These are not reasons to avoid self-hosting. They are reasons to self-host deliberately, with an understanding of what you are taking on.

What I Learned

Financial data models are deceptively complex. A simple "amount and category" model breaks down quickly when you add transfers, multi-currency, recurring transactions, and budgets. The data model needs to be consistent: a transfer between two accounts must debit one and credit the other atomically.

Security features need to be there from the start. Adding authentication, rate limiting, and audit logging after the fact is much harder than designing them in from the beginning. A finance application without audit logging is a liability.

Self-hosting is a feature and a constraint. It is a feature because it gives users control over their data. It is a constraint because it limits the audience to people who can manage a server. Accepting that tradeoff honestly is better than pretending it does not exist.

DepanceAPP is not trying to replace every cloud-based finance tool. It is trying to be a good option for people who prefer to keep their financial data private and are willing to maintain their own infrastructure to do it.