A finance tracking app that parses bank PDF ledgers, categorizes transactions with smart suggestions, and displays data in an interactive dashboard.
- Privacy-first: All data stays local in a SQLite database
- Multi-user support: User authentication with secure session management
- Multi-workspace: Organize finances into separate workspaces with role-based access (owner, editor, viewer)
- Collaboration: Invite users to workspaces via username or email
- Multilingual: English and Portuguese UI with user locale preference
- Password reset: Reset your password via email
- Workspace backup/restore: Export and import workspace data
- Error handling: Structured error codes, session expiry detection, React error boundaries
- PDF parsing: Extract transactions from Novo Banco and CGD monthly statements (extensible to other banks)
- Smart categorization: Pattern-based category suggestions with user-defined categories
- Dashboard: View data by year, month, and category with charts
- Financial reports: Annual and monthly reports with visualizations
- Recurring detection: Automatic detection and management of recurring transactions
- Bank-agnostic architecture: Support for multiple banks with bank-specific patterns
- Frontend: React + TypeScript + Vite + react-i18next
- UI: Tailwind CSS + Recharts
- Backend: Node.js + Express
- Database: SQLite (better-sqlite3)
- PDF Parsing: pdfjs-dist
compasso/
├── apps/
│ ├── web/ # React frontend
│ └── api/ # Node.js backend
├── packages/
│ └── shared/ # Shared types & constants
├── data/ # SQLite database (gitignored)
└── uploads/ # Temporary PDF uploads (gitignored)
- Node.js 22+
- npm 10+
# Install dependencies
npm install
# Build shared package
npm run build -w @compasso/shared# Run both frontend and backend in development mode
npm run dev- Frontend: http://localhost:5180
- Backend: http://localhost:5181
Create a .env file in the root or apps/api directory:
# Server configuration
PORT=5181
HOST=127.0.0.1
NODE_ENV=development
# CORS configuration (comma-separated origins for production)
ALLOWED_ORIGINS=http://localhost:5180,http://127.0.0.1:5180
# Database path (optional, defaults to ./data)
DATABASE_PATH=./data
# Cookie security (defaults to true in production, set to false for HTTP-only deployments)
# SECURE_COOKIES=false
# Email — optional, used for password reset emails (see below)
# Option 1: SMTP (any provider)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=user@gmail.com
SMTP_PASS=app-password
SMTP_FROM=noreply@yourdomain.com
# Option 2: Resend
RESEND_API_KEY=re_xxxxxxxxxxxx
EMAIL_FROM=noreply@compasso.appPassword reset emails require an email transport. Without configuration the app runs normally but password reset will not send emails. Configure one of the following options:
Works with Gmail, Mailgun, SES, self-hosted, or any SMTP server.
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587 # defaults to 587
SMTP_SECURE=false # true for port 465
SMTP_USER=user@gmail.com
SMTP_PASS=app-password
SMTP_FROM=noreply@yourdomain.comSMTP_FROM defaults to noreply@compasso.app if not set.
Uses the Resend REST API.
- Create an account at resend.com
- Generate an API key from the dashboard
- Verify a sending domain (or use the sandbox domain for development)
- Set the environment variables:
RESEND_API_KEY=re_xxxxxxxxxxxx
EMAIL_FROM=noreply@yourdomain.comEMAIL_FROM defaults to noreply@compasso.app if not set.
If both are configured, SMTP takes priority.
npm run buildThe Docker image is automatically built and pushed to GitHub Container Registry on every push to main (after CI passes). The container runs as a non-root user (node) for security.
# Run with the pre-built image
docker compose up -d
# Pull the latest image (e.g., after a new release)
docker compose pull && docker compose up -dTo build locally instead:
docker build -t compasso .
docker run -d -p 5181:5181 -v ./data:/data compassoEnvironment variables are read from a .env file in the project root.
For HTTP-only deployments without a reverse proxy, set SECURE_COOKIES=false in your environment — otherwise session cookies won't be sent by the browser:
environment:
- SECURE_COOKIES=false
- ALLOWED_ORIGINS=http://YOUR_IP:5181If you run Watchtower, it will automatically detect new image pushes and update the running container:
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_POLL_INTERVAL=3600
- WATCHTOWER_CLEANUP=true
restart: unless-stoppedFor private repositories, authenticate Docker with GHCR first:
echo "YOUR_GITHUB_PAT" | docker login ghcr.io -u YOUR_USERNAME --password-stdin- Create an account: Register a new user account on the login page
- Create a workspace: Set up a workspace for your finances (one is created by default)
- Upload a statement: Go to the Upload page and select your bank, then drag & drop a PDF statement
- Review transactions: Review the parsed transactions and adjust categories as needed
- View dashboard: See your financial overview with charts and statistics
- Manage categories: Add custom categories and patterns for auto-categorization
- View reports: Access annual and monthly financial reports
- Track recurring: View and manage detected recurring transaction patterns
- Novo Banco (Portugal) - "Extrato Integrado" PDF format
- CGD (Caixa Geral de Depósitos) (Portugal)
The project uses a registry pattern — 4 files need to be touched:
- Create parser data in
apps/api/src/parsers/<bank-slug>-data.ts(exports config, patterns) - Create parser in
apps/api/src/parsers/<bank-slug>.ts(imports data, exportsBankParserDefinitionwith parse function) - Register data in
apps/api/src/parsers/registry.ts(1 import + 1 array entry) - Write tests in
apps/api/src/parsers/<bank-slug>.test.ts
See .github/BANK_PARSER_GUIDE.md for the full contributor guide.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register | Create new user account |
| POST | /api/auth/login | Authenticate user |
| POST | /api/auth/logout | End user session |
| GET | /api/auth/me | Get current user info |
| PUT | /api/auth/profile | Update user profile |
| PUT | /api/auth/password | Change password |
| POST | /api/auth/forgot-password | Request password reset email |
| POST | /api/auth/reset-password | Reset password with token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/workspaces | List user's workspaces |
| POST | /api/workspaces | Create workspace |
| GET | /api/workspaces/:id | Get workspace details |
| PUT | /api/workspaces/:id | Update workspace |
| DELETE | /api/workspaces/:id | Delete workspace |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/workspaces/:id/members | List workspace members |
| PUT | /api/workspaces/:id/members/:userId | Change member role |
| DELETE | /api/workspaces/:id/members/:userId | Remove member |
| POST | /api/workspaces/:id/invitations | Invite user to workspace |
| GET | /api/workspaces/:id/invitations | List pending invitations |
| GET | /api/invitations | My pending invitations |
| POST | /api/invitations/:id/accept | Accept invitation |
| POST | /api/invitations/:id/decline | Decline invitation |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/dashboard | Dashboard summary data |
| GET | /api/dashboard/years | Available years with data |
| GET | /api/reports/yearly | Annual financial report |
| GET | /api/reports/category-trends | Category trends report |
| GET | /api/reports/years | List available report years |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/transactions | List transactions (with filters) |
| GET | /api/transactions/export | Export transactions as CSV |
| POST | /api/transactions/confirm | Save parsed transactions |
| PUT | /api/transactions/:id | Update transaction category |
| DELETE | /api/transactions/:id | Delete transaction |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/upload | Upload and parse PDF |
| GET | /api/upload/banks | List supported banks |
| GET | /api/upload/ledgers | List uploaded ledgers |
| DELETE | /api/upload/ledgers/:id | Delete ledger |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/categories | List categories |
| POST | /api/categories | Create category |
| GET | /api/categories/:id | Get category with patterns |
| PUT | /api/categories/:id | Update category |
| DELETE | /api/categories/:id | Delete category |
| POST | /api/categories/:id/patterns | Add pattern to category |
| POST | /api/categories/:id/patterns/quick | Create quick pattern |
| DELETE | /api/categories/:id/patterns/:patternId | Delete pattern |
| GET | /api/categories/patterns/exists | Check if pattern exists |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/recurring | List recurring patterns |
| POST | /api/recurring/detect | Detect recurring patterns |
| PUT | /api/recurring/:id | Toggle pattern active status |
| GET | /api/recurring/:id/transactions | List pattern transactions |
| DELETE | /api/recurring/:id | Delete recurring pattern |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/backup/export | Export workspace data |
| POST | /api/backup/import | Import workspace data |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health | Health check |
# Run all tests
npm test
# Run tests with coverage
npm test -- --coverageTests use Vitest and cover parsers, services, middleware, and error handling.