- JavaScript 48.2%
- TypeScript 25.5%
- CSS 15.9%
- HTML 8.9%
- Dockerfile 1.5%
| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| .dockerignore | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
Haushaltsbuch
Ein persönliches, lokal gehostetes Haushaltsbuch: Einkommen und Fixkosten eintragen, Tagesausgaben pflegen und den verbleibenden Monatsbudget im Blick behalten – inklusive kleiner Gamification (Maskottchen & Achievements) und einer Auswertungsseite mit Diagrammen.
Features
- Mehrere Einkommensquellen und Fixkosten mit frei einstellbaren Kategorien
- Tagesaktuelle Ausgabenerfassung mit laufendem Restbetrag für den Monat
- Einmalige Einkommensanpassungen (Bonus, Gehaltskürzung) für einen einzelnen Monat, ohne die Vorlage zu ändern
- Maskottchen (Kuh/Drache/Hai), das je nach Sparverhalten reagiert, plus ein Achievement-System
- Auswertungsseite mit Einkommen/Ausgaben-Verlauf und einer Kategorie-Aufschlüsselung ("Wohin dein Geld fließt")
Tech-Stack
- Node.js + Express (
backend/server.js) - Datenzugriff über Knex (
backend/db.js,backend/db-config.js), Backend je nachDATABASE_URL: SQLite überbetter-sqlite3lokal (Datenbankdatei wird beim ersten Start automatisch angelegt), Postgres in Produktion. Schema-Änderungen laufen über Knex-Migrationen (backend/migrations/,npm run migrate:make <name>) - Reines HTML/CSS/JavaScript im Frontend (
frontend/), kein Build-Schritt, kein Framework
Lokal starten (ohne Docker)
Voraussetzung: Node.js 24 (siehe Dockerfile).
cd backend
npm install
npm start
Die App läuft danach unter http://localhost:3000. Die SQLite-Datenbank wird als backend/haushaltsbuch.db angelegt (per .gitignore ausgeschlossen).
Mit Docker starten
docker compose up -d --build
Die App läuft danach unter http://localhost:3000. Die Datenbank wird im benannten Volume haushaltsbuch-data gespeichert und bleibt über Container-Neubauten hinweg erhalten.
Zum Stoppen:
docker compose down
(Das Volume bleibt dabei erhalten; docker compose down -v würde es zusätzlich löschen.)
Das Compose-File enthält bereits Traefik-Labels für den Betrieb hinter einem Traefik-Reverse-Proxy. Dafür muss das externe Docker-Netzwerk existieren, auf dem auch Traefik lauscht (Standard: traefik, per TRAEFIK_NETWORK anpassbar), und TRAEFIK_HOST auf die gewünschte Domain gesetzt werden (siehe Tabelle unten).
Zugriffsschutz per Client-Zertifikat (mTLS)
Statt Basic Auth (das bei installierten iOS-PWAs wegen des WebKit-Zertifikatscaches bei jedem Neustart erneut nach Zugangsdaten fragt) verlangt der Router ein gültiges Client-Zertifikat, bevor überhaupt eine TLS-Verbindung zustande kommt. Kein Passwort-Prompt mehr – das Gerät weist sich über das installierte Zertifikat aus.
Die CA, die Skripte zum Ausstellen von Geräte-Zertifikaten und die eigentliche TLS-Options-Definition (inkl. clientAuth) liegen nicht in diesem Repo, sondern im separaten Repo mtls-ca – sie sind app-übergreifend, ein einmal ausgestelltes Zertifikat gilt für alle Apps, die dieselbe CA referenzieren (z. B. auch dex-binder). Wichtig: Ein tls.options-Eintrag mit clientAuth-Einstellungen lässt sich über Docker-Labels nur referenzieren, nicht definieren – die Definition muss über Traefiks File-Provider laufen (siehe mtls-ca-Repo). Dieses App-Repo referenziert die Option daher mit dem Suffix @file (traefik.http.routers.haushaltsbuch.tls.options=haushaltsbuch-mtls@file), nicht @docker.
Konfiguration
| Variable | Beschreibung | Standard |
|---|---|---|
DATABASE_URL |
Postgres-Connection-String; wenn gesetzt, wird Postgres statt SQLite verwendet | nicht gesetzt (SQLite) |
DB_PATH |
Pfad zur SQLite-Datenbankdatei (nur wenn DATABASE_URL nicht gesetzt ist) |
backend/haushaltsbuch.db |
PORT |
Port, auf dem der Server lauscht | 3000 |
HOST_PORT |
Host-Port, auf den der Container-Port gemappt wird (Docker Compose) | 3000 |
TRAEFIK_HOST |
Domain, unter der Traefik die App erreichbar macht | haushaltsbuch.example.com |
TRAEFIK_NETWORK |
Name des externen Docker-Netzwerks, auf dem Traefik lauscht | traefik |
TRAEFIK_CERT_RESOLVER |
Name des in Traefik konfigurierten Cert-Resolvers | letsencrypt |
API
Alle Endpunkte liefern/erwarten JSON.
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /api/health |
Health-Check ({"status":"ok","database":"connected"}, 503 falls die DB-Prüfung fehlschlägt) |
| GET | /api/version |
Git-Commit-Hash, beim Build eingebacken ({"version":"<hash>"}, "unknown" außerhalb von Docker) |
| GET | /api/summary?month=YYYY-MM |
Einkommen, Fixkosten, Ausgaben, Rest, Maskottchen für einen Monat |
| GET | /api/achievements |
Freigeschaltete Achievements über alle Monate |
| GET | /api/analytics?range=N |
Monatlicher Verlauf + Kategorie-Aufschlüsselung der letzten N Monate |
| GET/POST/DELETE | /api/categories[/:id] |
Kategorien verwalten |
| POST/PUT/DELETE | /api/incomes[/:id] |
Einkommensquellen verwalten |
| POST/DELETE | /api/income-adjustments[/:id] |
Einmalige Einkommensanpassungen |
| POST/PUT/DELETE | /api/fixed-costs[/:id] |
Fixkosten verwalten |
| POST/PUT/DELETE | /api/expenses[/:id] |
Tagesausgaben verwalten |
Projektstruktur
backend/
server.js Express-Server, API-Routen, Business-Logik (Summary, Maskottchen, Achievements, Analytics)
db.js Knex-Instanz, Migrations-Bootstrap (baselined bestehende Datenbanken automatisch)
db-config.js Knex-Connection-Config (SQLite lokal, Postgres via DATABASE_URL)
seed.js Idempotentes Seeding (Settings-Zeile, Standardkategorien) für frische Datenbanken
migrations/ Knex-Migrationen (Schema-Historie)
frontend/
index.html Dashboard (Summary-Karten, Maskottchen, Achievements, Ausgaben, Einstellungen)
auswertung.html Auswertungsseite mit Diagrammen
app.js Frontend-Logik für das Dashboard
analytics.js Diagramm-Rendering (SVG) für die Auswertungsseite
styles.css Gemeinsames Styling