Subscription and recurring expense tracker โ never lose track of your monthly costs again.
SubTrack lets you log subscriptions (Netflix, Spotify, gym memberships, cloud storageโฆ) and see what they cost you monthly and yearly, with multi-currency conversion, smart reminders, sharing, and analytics that actually answer questions you'd ask yourself.
Live app โ https://subtrack-psi-nine.vercel.app
๐ฎ No sign-up required โ click "Explore the demo" on the login page to drop straight into a fully-seeded account (subscriptions, budgets, analytics and spend history).
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
- ๐ Dashboard with monthly/yearly totals, upcoming payments, recent activity, and a quick "review these" widget
- ๐ธ Per-subscription cost breakdown (per cycle, monthly equivalent, yearly projection, optional yearly-vs-monthly savings hint)
- ๐ช Multi-currency display with live FX (Frankfurter) โ default picked from your browser locale
- ๐ Powerful list view: search, filter, sort, bulk actions, command palette, keyboard shortcuts (
?for the cheat sheet) - ๐ English + Turkish UI,
<html lang>kept in sync for screen readers - ๐จ Glassmorphism dark theme with light option, installable as a PWA, works offline for cached views
- ๐ช 3D auth & onboarding hero with real subscription-platform logos (react-three-fiber) โ lightweight mobile variant + reduced-motion / WebGL fallbacks
- ๐ฎ One-click demo โ "Explore the demo" provisions a throwaway, fully-seeded account (subscriptions, budgets, price history, spend snapshots) that auto-expires after 48h, so anyone can try the app without registering
- ๐ Price history per subscription with stepped chart + total/last-change percent
- ๐ Payment heatmap (year-long GitHub-style grid showing days you'll pay)
- ๐ฎ Linear-regression forecast of next 6 months based on monthly snapshots
- ๐ Period comparison ("now vs then" totals + per-category delta + added/cancelled list)
- ๐ฐ Budgets with TOTAL or per-CATEGORY scope, MONTHLY/YEARLY periods, and 80%/100% threshold alerts (email + in-app + push)
- ๐ง Smart category suggestions as you type a name; bulk auto-categorize for uncategorized rows
- ๐จ Suggestions widget flags expensive subs, price hikes, expired trials, unused-recently rows, and duplicate categories โ with 30-day dismiss
- ๐ง Renewal & trial-ending email reminders via SMTP (Nodemailer)
- ๐ In-app notification center (bell icon, unread badge, accept/decline invitations inline)
- ๐ฒ Web Push via VAPID with installable service worker
- ๐๏ธ Calendar feed (
webcal://+.icsper subscription, Google/Outlook quick-add)
- ๐ฅ Per-subscription sharing with VIEWER / EDITOR roles
- โ๏ธ Invitation by email with single-use 7-day tokens; in-app pending invitations on the bell
- ๐ก๏ธ Email-bound acceptance: only the invited email can take the share
- ๐ 2FA (TOTP) via authenticator apps with single-use backup codes
- ๐ OAuth login (Google + GitHub) with link-by-email account merge
- ๐ Server-side JWT revocation via
tokenVersionโ logout, password change, and 2FA toggles invalidate every live token - ๐ค Full account data export (one JSON file of every row you own, password-hashโfree)
- ๐๏ธ Account deletion behind password + 2FA + typed-DELETE confirmation
- ๐ Sentry on both server (Express handler) and client (React ErrorBoundary), user context auto-attached
- ๐ Pino structured logs on the server; HTTP request log middleware
- ๐ Activity log page with type chips, search, date range filtering
Frontend โ React 19 ยท TypeScript ยท Vite ยท TailwindCSS ยท shadcn/ui ยท TanStack Query ยท Zustand ยท Recharts ยท Framer Motion ยท react-three-fiber / three.js ยท simple-icons ยท i18next ยท Storybook ยท Sentry React.
Backend โ Node.js 22 ยท Express 5 ยท TypeScript ยท PostgreSQL ยท Prisma ยท JWT (with server-side revocation) ยท Pino ยท Nodemailer ยท web-push ยท otplib ยท Sentry Node.
DevOps โ Docker ยท docker-compose (Postgres + Adminer + Mailhog) ยท GitHub Actions (type-check ยท lint ยท tests ยท build) ยท ESLint ยท Vitest (53 server unit tests + client tests).
flowchart LR
User[๐ค User browser] -- "HTTPS" --> Vercel
User -- "Web Push (VAPID)" --> SW[Service Worker]
subgraph Vercel["Vercel ยท Edge"]
Client[Vite static SPA<br/>React + Tailwind]
end
subgraph Render["Render ยท Docker container"]
API[Express + Prisma<br/>JWT ยท cookie-parser ยท helmet]
Cron[/Cron triggers<br/>/jobs/notify-renewals<br/>/jobs/snapshot-monthly/]
end
subgraph Neon["Neon ยท Postgres 16"]
DB[(subtrack_db<br/>users ยท subscriptions<br/>budgets ยท price_history<br/>events ยท shares ...)]
end
Mail[(SMTP / Mailhog)]
Sentry[(Sentry)]
Frankfurter[(Frankfurter FX API)]
Client -- "/api/* (Vercel rewrite)" --> API
API -- "Prisma" --> DB
API -- "Renewal & invitation emails" --> Mail
API -- "Web push payloads" --> SW
API -- "FX rates" --> Frankfurter
API -. "errors / traces" .-> Sentry
Client -. "errors / traces" .-> Sentry
Cron -- "X-Job-Key" --> API
The same SPA is served from Vercel's CDN; every /api/* request is rewritten
to the Render API, so the browser always sees same-origin requests (no CORS
preflight headaches). Push delivery goes from the API straight to the
browser's service worker via VAPID, bypassing Vercel.
# 1. Bring up Postgres + Mailhog + Adminer
npm run docker:up
# 2. Install deps
npm install
# 3. Server: set env, migrate, run
cd apps/server
cp .env.example .env # fill JWT_SECRET and INTERNAL_JOB_KEY
npx prisma migrate dev
npm run dev
# 4. Client (separate terminal)
cd apps/client
npm run devVisit http://localhost:5173 for the app, http://localhost:8025 for the
Mailhog inbox, and http://localhost:8080 for Adminer (Postgres GUI).
# Workspace
npm run type-check # tsc across all workspaces
npm run lint # eslint across client + server
npm run docker:up | down # toggle local Postgres / Mailhog / Adminer
# Server (cd apps/server)
npm test # vitest, 53 unit tests
npm run test:coverage # v8 coverage report
npx prisma studio # browse the DB
# Client (cd apps/client)
npm run dev | build # vite
npm run storybook # component catalog on :6006
npm run check:locales # sanity-check TR/EN parityThe recommended free stack is Neon (Postgres) + Render (API) + Vercel (client). All three have permanent free tiers and no credit card required to start.
- Sign up at https://neon.tech with GitHub.
- Create a new project (region close to Render is ideal โ Frankfurt for
this repo's
render.yaml). - Copy the pooled connection string (it contains
-pooler). You'll paste it into Render below asDATABASE_URL.
-
Sign up at https://render.com with GitHub.
-
New + โ Blueprint โ connect this repo. Render reads
render.yamland provisions awebservice namedsubtrack-apifromapps/server/Dockerfile. -
After the service is created, set the env vars that were left blank:
Key Value DATABASE_URLThe Neon pooled connection string APP_URLThe Vercel URL from step 3 CORS_ORIGINSame as APP_URLOAUTH_CALLBACK_BASE_URLThe Render URL (e.g. https://subtrack-api.onrender.com)SMTP / VAPID / OAuth / Sentry Optional โ leave blank to disable that feature -
Render redeploys. On boot the container runs
prisma migrate deployautomatically, so the schema is created on first launch.
Heads up โ Render's free Web Service spins down after 15 minutes of inactivity and takes ~30s to wake. A scheduled GitHub Actions workflow (
.github/workflows/keep-alive.yml) pings/healthevery ~10 min to keep it warm; for guaranteed always-on hosting move to a paid Render plan or Fly.io, or use an external pinger like UptimeRobot.
- Sign up at https://vercel.com with GitHub.
- New Project โ import the repo โ set Root Directory to
apps/client. Vercel detects Vite automatically. - Open
apps/client/vercel.jsonand replace theCHANGE-MEplaceholder with your actual Render URL, then commit. Vercel auto-redeploys. - (Optional) Add a
VITE_SENTRY_DSNenv var for browser error reporting.
The Vercel build outputs dist/; the rewrites block in vercel.json
proxies /api/* to your Render API and rewrites every other path to
index.html so client-side routing works.
-
https://<render-url>/healthreturns{"status":"ok"}. - Open
https://<vercel-url>and either register or click "Explore the demo". - Confirm the user lands at
/dashboardand the bell, sidebar, topbar render. - If using OAuth: update Google/GitHub OAuth app callbacks to
https://<render-url>/api/auth/oauth/<provider>/callback. - If using SMTP: send a test renewal job with
curl -X POST https://<render-url>/api/jobs/notify-renewals -H "X-Job-Key: <INTERNAL_JOB_KEY>".
Everything in this section is wired up but disabled by default. Add the env vars below to Render to switch each one on.
| Feature | What you need | Render env vars |
|---|---|---|
| SMTP email | A Resend, Postmark, or any SMTP provider account | SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE, EMAIL_FROM, APP_URL |
| Web push | A VAPID key pair (node -e "console.log(require('web-push').generateVAPIDKeys())") |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT |
| Google OAuth | A Google Cloud OAuth client with the Render callback URL whitelisted | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, OAUTH_CALLBACK_BASE_URL |
| GitHub OAuth | A GitHub OAuth app | GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, OAUTH_CALLBACK_BASE_URL |
| Sentry server | A Sentry project DSN | SENTRY_DSN, SENTRY_TRACES_SAMPLE_RATE |
| Sentry client | Same DSN, set on Vercel instead | VITE_SENTRY_DSN (on Vercel) |
| Renewal cron | Any cron service (Render Cron, cron-job.org, GitHub Actions) | Hit POST /api/jobs/notify-renewals daily with X-Job-Key: <INTERNAL_JOB_KEY> |
| Monthly snapshot cron | Same | Hit POST /api/jobs/snapshot-monthly on the 1st of each month |
| Budget-alert cron | Same | Hit POST /api/jobs/check-budgets on your preferred cadence |
| Demo cleanup cron | Same (optional โ demos also self-prune on each new demo) | Hit POST /api/jobs/cleanup-demo daily to remove demo accounts >48h old |
subtrack/
โโโ apps/
โ โโโ client/ React + Vite SPA
โ โ โโโ src/
โ โ โ โโโ features/ feature-scoped folders (auth, budgets, shares, โฆ)
โ โ โ โโโ components/ shared UI primitives
โ โ โ โโโ pages/ top-level routes
โ โ โ โโโ store/ Zustand stores
โ โ โ โโโ lib/ api client, i18n, sentry, formatters
โ โ โโโ public/ static assets + sw.js + manifest.webmanifest
โ โ โโโ .storybook/ component catalog config
โ โโโ server/ Express + Prisma API
โ โโโ src/
โ โ โโโ controllers/ route handlers
โ โ โโโ services/ business logic (forecast, comparison, โฆ)
โ โ โโโ lib/ primitives (jwt, ics, totp, fx, sentry, โฆ)
โ โ โโโ middlewares/ auth, rate-limit, error handler
โ โ โโโ routes/ Express routers
โ โโโ prisma/ schema + migrations
โโโ packages/shared/ cross-app TypeScript types
โโโ docs/screenshots/ README image gallery
โโโ docker-compose.yml local Postgres + Mailhog + Adminer
โโโ render.yaml one-click Render blueprint
โโโ apps/client/vercel.json Vercel rewrites + headers
MIT โ see LICENSE.





