De Express-app serveert nu zelf de gebouwde Vite-frontend op / met een SPA-fallback naar index.html; de aparte nginx-container is vervallen. Bedoeld voor self-hosting op een Raspberry Pi: één image, één origin, geen interne proxy en geen CORS tussen frontend en API. Docker - Nieuwe multi-stage Dockerfile in de root met targets test, development en production; frontend/Dockerfile, frontend/nginx-spa.conf en backend/Dockerfile verwijderd - docker-compose.yml: alleen app + postgres, app publiceert APP_PORT (standaard 3400) naar containerpoort 3001 - docker-compose.dev.yml: dev-container draait API (node --watch) en de Vite dev-server samen via docker/dev-entrypoint.sh, hot reload intact - .dockerignore, .env.example, .gitignore en .gitattributes toegevoegd Backend - express.static + SPA-fallback op STATIC_DIR (default /app/public), met immutable caching voor gehashte assets en no-store voor index.html - Rate limiting alleen nog op /api — statische bestanden liepen anders na 200 requests tegen de limiet aan - Helmet CSP toegespitst op wat de frontend laadt (Google Fonts, inline styles, base64-avatars); upgrade-insecure-requests uit voor intern http - Schema-init verplaatst naar initDb(), niet meer bij import van db.js Tests en CI/CD - backend/test/app.test.js: 11 tests (node --test) voor API-routes, SPA-fallback, cache- en security-headers, zonder database - test.ps1 / test.sh: backend-tests, productiebuild en smoketest op de echte app + postgres stack - azure-pipelines.yml: Test -> Build (incl. smoketest) -> Publish (multi-arch amd64 + arm64, alleen op main/master of een tag) Scripts en documentatie - dev.ps1/dev.sh met targets app en postgres, acties logs en test; dev.ps1 up wist de dev-database niet langer (alleen dev.ps1 down) - release.ps1/release.sh bouwen één image voor amd64 + arm64 en draaien de testsuite vóór het publiceren - README.md en BEHEER.md bijgewerkt voor de nieuwe structuur Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
💜 VibeFinance
Persoonlijk vermogensdashboard
Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer
🤖 Gebouwd met Claude.ai
Deze applicatie is volledig ontworpen en ontwikkeld met behulp van Claude.ai — de AI-assistent van Anthropic. Van architectuur tot gebruikersinterface: alles is stap voor stap tot stand gekomen in samenwerking met Claude.
⚡ Quick Start
Vereisten: Docker Engine 24+ en Docker Compose v2+
De app draait in 2 containers: app (Express API + gebouwde frontend) en
postgres (database).
.env — kopieer .env.example en vul in:
POSTGRES_USER=vibefinance
POSTGRES_PASSWORD=kies_een_sterk_wachtwoord
JWT_SECRET=minimaal_64_willekeurige_tekens
APP_PORT=3400
# Genereer een veilige JWT_SECRET
openssl rand -hex 64
cp .env.example .env # en vul de waarden in
docker compose pull && docker compose up -d
Navigeer naar http://<server-ip>:3400 en registreer je account. De eerste geregistreerde gebruiker krijgt automatisch de Admin-rol.
De container luistert intern op poort 3001 en serveert daar zowel de frontend
(op /) als de API (op /api). APP_PORT bepaalt de poort op de host.
Development (hot reload)
.\dev.ps1
Eén dev-container draait de API (node --watch) én de Vite dev-server samen:
| Adres | Wat |
|---|---|
| http://localhost | de app in de browser (Vite, hot reload) |
| http://localhost:3001/api/health | de API rechtstreeks |
Vite proxyt /api naar de API in dezelfde container, dus je hebt geen CORS-
uitzonderingen nodig. Handige acties:
.\dev.ps1 up -Target app # alleen de app herbouwen en herstarten
.\dev.ps1 logs # logs van de app volgen
.\dev.ps1 down # stoppen én volumes wissen (dev-database weg)
.\dev.ps1 test # testsuite draaien
.\dev.ps1 uplaat de dev-database staan; alleen.\dev.ps1 downwist de volumes.
Testen
.\test.ps1 # backend-tests + productiebuild + smoketest
.\test.ps1 -Unit # alleen de backend-tests
.\test.ps1 -NoSmoke # tests + build, zonder de stack te starten
Alles loopt in Docker — Node op de host is niet nodig. De backend-tests draaien
zonder database via het test-target in de Dockerfile.
Productie release
$env:GITEA_TOKEN = "<token met release-rechten>"
.\release.ps1
Draait de tests, bouwt het app-image voor amd64 + arm64 (Raspberry Pi),
pusht naar de registry, zet de Git-tag en publiceert de Gitea-release.
🗂️ Functionaliteit
| Scherm | Wat het doet |
|---|---|
| 📊 Dashboard | Overzicht van totaal eigen vermogen, schulden en netto vermogen |
| 💰 Eigen vermogen | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek |
| 📉 Schulden | Schulden bijhouden per blok met afbetalingsoverzicht |
| 📈 Voortgang | Grafieken en trends van EV en schuld over tijd (apart bij te houden) |
| 👥 Gebruikers | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) |
🏗️ Architectuur
vibefinance/
├── Dockerfile # Multi-stage: frontend build → app-image
├── .dockerignore
├── docker-compose.yml # Productie: app + postgres
├── docker-compose.dev.yml # Dev: app (API + Vite) + postgres
├── azure-pipelines.yml # CI/CD: test → build+smoketest → publish
├── .env.example # Kopieer naar .env en vul in
├── package.json # Versienummer — single source of truth
├── dev.ps1 / dev.sh # Dev script (bouwen, starten, logs, pushen)
├── test.ps1 / test.sh # Testsuite (unit + build + smoketest)
├── release.ps1 / release.sh # Release (test, push, tag, Gitea-release)
│
├── docker/
│ └── dev-entrypoint.sh # Start API + Vite samen in de dev-container
│
├── frontend/ # React / Vite SPA (build → dist)
│ ├── vite.config.js # /api proxy naar de API in dezelfde container
│ └── src/
│ ├── App.jsx # Root – wiring + idle-logout
│ ├── main.jsx
│ ├── constants/ # Kleuren, tabs, begindata
│ ├── utils/ # Formatters, helpers
│ ├── hooks/ # useTheme, useUpdateCheck
│ ├── context/ # AppContext (global state + API sync)
│ ├── components/
│ │ ├── ui/ # Primitieve UI (VCard, DonutChart…)
│ │ ├── LoginPage.jsx
│ │ ├── NavBar.jsx
│ │ ├── ProfielPopup.jsx
│ │ └── GebruikersBeheer.jsx
│ └── pages/
│ ├── DashboardTab.jsx
│ ├── EigenVermogenTab.jsx
│ ├── SchuldTab.jsx
│ └── VoortgangTab.jsx
│
└── backend/ # Express REST API + statische frontend
├── test/ # node --test integratietests
└── src/
├── server.js # Entry point: API op /api, SPA op /
├── db.js # PostgreSQL pool + initDb() schema-migraties
├── config/ # Omgevingsvariabelen (incl. STATIC_DIR)
├── constants/ # Begindata (initialData.js)
├── middleware/ # auth.js, errorHandler.js
├── routes/ # auth, data, users, health, update-check
└── stores/ # userStore, dataStore
Stack: React 18 · Vite · Node.js 22 · Express 4 · PostgreSQL 18 · JWT · bcrypt · Docker Compose v2
Waarom 2 containers
De frontend werd eerder door een eigen nginx-container geserveerd. Voor self-hosting op een Raspberry Pi is dat een container te veel: de Express-app serveert de statische build nu zelf, dus er is geen aparte webserver en geen interne proxy meer nodig. Dat betekent ook: één origin, dus geen CORS tussen frontend en API.
| Verzoek | Wat de app doet |
|---|---|
/api/... |
REST API (rate limited) |
/assets/... |
Statische build, 1 jaar immutable gecached |
| al het overige | index.html — fallback voor React-routing |
🔌 API
Beveiligde endpoints vereisen Authorization: Bearer <token>. Rate limiting: 20 req/15 min op auth-routes, 200 req/15 min op de rest van /api (statische bestanden zijn uitgezonderd).
| Route | Methoden | Omschrijving |
|---|---|---|
/api/auth/register |
POST | Account aanmaken (eerste user = Admin) |
/api/auth/login |
POST | Inloggen, JWT token terug |
/api/auth/me |
GET, PATCH | Profiel ophalen en bijwerken |
/api/auth/me/password |
POST | Wachtwoord wijzigen |
/api/data |
GET, PUT, DELETE | Financiële data per gebruiker |
/api/users |
GET, POST, PATCH, DELETE | Gebruikersbeheer (Admin only) |
/api/health |
GET | Status + uptime |
/api/update-check |
GET | Laatste release (voor de update-melding) |
🗄️ Database
| Tabel | Omschrijving |
|---|---|
users |
Accounts met bcrypt hash, rol, actief-vlag en aanmaakdatum |
user_data |
Financiële data per gebruiker (JSON) |
| Omgeving | Volume | Mount pad |
|---|---|---|
| Productie | vibefinance_pgdata |
/var/lib/postgresql |
| Development | postgres_dev_data |
/var/lib/postgresql |
PostgreSQL 18 gebruikt
PGDATA=/var/lib/postgresql/18/docker— het volume is daarom gemount op/var/lib/postgresql(niet/data).
Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via
initDb() in backend/src/db.js.
⚙️ Omgevingsvariabelen
| Variabele | Standaard | Omschrijving |
|---|---|---|
POSTGRES_PASSWORD |
— | Verplicht |
JWT_SECRET |
— | Verplicht — minimaal 64 tekens |
POSTGRES_USER |
vibefinance |
Database gebruikersnaam |
POSTGRES_DB |
vibefinance |
Database naam |
JWT_EXPIRES |
7d |
Geldigheidsduur token |
CORS_ORIGIN |
http://localhost |
Externe domeinnaam (één origin, dus zelden nodig) |
APP_PORT |
3400 |
Poort op de host (container luistert op 3001) |
PORT |
3001 |
Poort binnen de container |
STATIC_DIR |
/app/public |
Map met de frontend-build |
UPDATE_CHECK_URL |
Gitea releases-API | Bron update-melding (leeg = uit) |
UPDATE_CHECK_TOKEN |
— | Alleen voor een niet-publieke repo |
🔐 Beveiliging
- Wachtwoorden gehasht met bcrypt (cost factor 12)
- JWT authenticatie met instelbare vervaltijd
- Frontend en API op één origin — geen interne proxy, geen CORS-uitzonderingen
- Content-Security-Policy via Helmet, toegespitst op wat de app laadt
- Database bereikbaar alleen binnen het interne Docker netwerk
- App-container draait als non-root
- Rate limiting op
/api(statische bestanden uitgezonderd)
🧪 Testen
| Laag | Wat | Commando |
|---|---|---|
| Backend | API-routes, SPA-fallback, cache- en security-headers | .\test.ps1 -Unit |
| Build | Vite-build + productie-image | .\test.ps1 -NoSmoke |
| Integratie | Smoketest op de echte app + postgres stack | .\test.ps1 |
In CI draaien dezelfde stappen via azure-pipelines.yml.
🛠️ Beheer
Zie BEHEER.md voor commando's voor lokale ontwikkeling, releases, logs en database-toegang.
Privé gebruik · gebouwd met Claude.ai