Hold lets you track your passive investment portfolio as easily as possible - log your BUY orders and let it handle the rest.
NOTE: This project is developed using AI.
-
Simple & Smart
- Multi-currency support - Convert all activity to your desired currency on-the-fly.
- Stock-splits and dividends calculation - Adjust holdings and valuation automatically.
- Import/export all of your activity to/from CSV.
- Multi-user support - OIDC by default, optional demo user for first impression.
- Privacy mode - hide absolute numbers and keep only percentage changes.
-
Visually appealing
- Information boxes present current status.
- Performance chart shows portfolio progress over time.
- Frontend: Next.js 16 (App Router, Client Components), Tailwind CSS v4, Recharts (Interactive Charts), Lucide React (Icons)
- Backend: Next.js Route Handlers (API Routes)
- Database: SQLite via Prisma ORM
- Authentication: NextAuth.js (v5 Beta) - Support for OIDC and Mock Developer Login
- External APIs: Yahoo Finance (Asset prices via
yahoo-finance2), Frankfurter API (Exchange rates)
The following diagram illustrates how the application components interact:
graph TD
User([User Browser]) -->|HTTPS| Frontend[Next.js Frontend UI]
Frontend -->|API Calls| Backend[Next.js API Routes]
subgraph Backend Services
Backend -->|Auth Session| NextAuth[NextAuth.js]
Backend -->|User Queries| Prisma[Prisma ORM]
Prisma -->|Read/Write| SQLite[(SQLite Database)]
Backend -->|Portfolio Operations| Portfolio[Portfolio Logic]
Portfolio -->|Portfolio Queries| Prisma
Portfolio -->|Quotes| YahooFinance{{Yahoo Finance API}}
Portfolio -->|Exchange Rates| Frankfurter{{Frankfurter API}}
end
NextAuth -->|OIDC| ExternalOIDC[OIDC Provider]
/
βββ prisma/ # Database schema & migrations
β βββ schema.prisma # Prisma schema definition
β βββ migrations/ # SQLite database migrations
βββ src/
β βββ app/ # Next.js App Router
β β βββ api/ # Backend API Route Handlers
β β β βββ auth/ # NextAuth API configuration
β β β βββ finance/ # Live stock search endpoints
β β β βββ portfolio/ # Portfolio summary, history, import/export
β β β βββ transactions/# Individual transaction operations
β β βββ globals.css # Global Styles (Tailwind v4 imports)
β β βββ layout.tsx # Root layout & providers
β β βββ page.tsx # Dashboard main page (UI & client logic)
β βββ components/ # Shared React components
β β βββ Providers.tsx # NextAuth Session Provider wrapper
β βββ lib/ # Business logic & utilities
β β βββ db.ts # Prisma client instance
β β βββ finance.ts # Yahoo Finance & Frankfurter API clients
β β βββ portfolio.ts # Portfolio aggregation & performance math
β βββ auth.ts # NextAuth configuration & handlers
βββ public/ # Static assets
βββ Dockerfile # Multi-stage production build
βββ package.json # Project dependencies and scripts
- Node.js:
v20.xor higher - Package Manager:
pnpm
Clone the repository and navigate to the root directory.
pnpm installCreate a .env file in the root of the project:
# Connection string for SQLite database (resolved relative to prisma/ directory)
DATABASE_URL="file:hold.db"
# NextAuth secret key for encrypting session cookies (Generate with: `openssl rand -base64 32`)
AUTH_SECRET="replace-this-message-with-64-byte-long-secret-password-right-now"
# Enable local credentials-based fallback login (set to "false" in production)
ALLOW_DEMO_LOGIN=true
# (Optional) OpenID Connect OIDC provider configuration
# AUTH_OIDC_ISSUER="https://AUTH_PROVIDER"
# AUTH_OIDC_CLIENT_ID="AUTH_PROVIDER_CLIENT_ID"
# AUTH_OIDC_CLIENT_SECRET="AUTH_PROVIDER_CLIENT_SECRET"
# AUTH_OIDC_NAME="AUTH_PROVIDER_NAME"Apply migrations to setup your SQLite database file. This will automatically create the prisma/ folder and initialize hold.db:
npx prisma migrate devStart the Next.js development server:
pnpm devOpen http://localhost:3000 in your browser.
The project includes a multi-stage Dockerfile that builds an optimized production image (~100MB).
docker build -t hold .Or
docker pull gabay/holdTo prevent data loss when the container restarts, mount a host directory to /app/data where the SQLite database file (hold.db) is stored:
docker run -d \
-p 3000:3000 \
--name hold \
--env-file .env \
-v /absolute/path/to/local/db-folder:/app/data \
hold:latestImportant
Database Host Directory Permissions:
The container runs as a non-root user (node, UID 1000). Ensure your host directory /absolute/path/to/local/db-folder is writeable by UID 1000 or has appropriate read/write permissions.
Note
Automatic Database Initialization:
If the mounted host directory is empty on first startup, the container's entrypoint script will automatically copy a pre-migrated template database (hold.db) into it. Subsequent starts will preserve and use your existing data.
You can import transaction history in bulk via a CSV file. The CSV file must contain a header row.
| Column | Required | Description | Example Values |
|---|---|---|---|
symbol |
Yes | Stock ticker symbol compatible with Yahoo Finance | AAPL, VOO, CSPX.L |
type |
Yes | Transaction type (case-insensitive) | BUY, SELL |
quantity |
Yes | Number of shares transacted (float) | 10, 2.5 |
pricePerShare |
Yes | Price per share in the transaction currency (float). Alias: price |
175.50, 94.20 |
currency |
No | Currency of the transaction. Default to ticker symbol | USD, EUR, GBP |
transactionDate |
No | Date of the transaction. Defaults to today. Alias: date |
2023-10-25, 2024-01-12 |
symbol,type,quantity,pricePerShare,currency,transactionDate
AAPL,BUY,10,175.50,USD,2026-01-01
VOO,SELL,2,220.00,USD,2026-01-02
CSPX.L,BUY,5,102.30,EUR,2026-01-03The application supports standard OIDC providers. OIDC is configured through the environment variables.
- Create OAuth 2.0 Provider in the authentik admin interface.
- Set the Authorized Redirect URI to:
http://localhost:3000/api/auth/callback/oidc(or your hold instance's URL). - Configure these variables in
.env:AUTH_OIDC_ISSUER="https://AUTHENTIK_DOMAIN" AUTH_OIDC_CLIENT_ID="AUTHENTIK_CLIENT_ID" AUTH_OIDC_CLIENT_SECRET="AUTHENTIK_CLIENT_SECRET" AUTH_OIDC_NAME="authentik"
- Run Linter:
pnpm lint - Open Database Studio:
npx prisma studio(Visual explorer for your SQLite database) - Generate Prisma Client:
npx prisma generate(Run this after making changes toschema.prisma) - Create a Database Migration:
npx prisma migrate dev --name <migration_name> - Run Tests:
pnpm test - Run Tests with coverage:
pnpm test --coverage