MyDebts
Personal projectA complete demo of an application for managing debts between individuals, with debts automatically flagged as overdue and shareable links that need no sign-up.

Remembering who owes what shouldn't depend on memory
Debts between friends and family are usually handled informally: a mental note, a message thread, a page in a notebook... until someone forgets and bringing it up becomes awkward. MyDebts is a simple, clear application for recording individual debts, each with its own due date, so nothing depends on memory.
- Built solo, in evenings and weekends, in my free time. The architecture had to stay simple so development was easy to pick back up after long gaps.
- Free-tier hosting throughout the project
- A complete demo: real authentication, password reset and bot protection.
- A real live deployment, reachable by anyone I shared it with, not just a local build.
A zoneless Angular 20 frontend backed by a small Express API.
Angular 20 with standalone components throughout, using zoneless change detection and signals for local state instead of a state library. The backend is Express 5 with MongoDB via Mongoose, validated with Joi, plus a full JWT auth flow — register, login, password reset — protected by Cloudflare Turnstile.
Five decisions, and what each one cost.
Zoneless Angular, not the default change detection
Angular 20 shipped zoneless change detection as a real option, not just an experiment, and I wanted to build directly on it rather than on the zone.js default most tutorials still teach. Local signal() calls replace NgRx: the app didn't need a state management library, so I didn't add one. YAGNI applied to an architectural decision, not just to a stray feature.
A visible cold start, not a hidden one
The backend sleeps on Render's free tier, so the first request after a period of inactivity can take several seconds. A request interceptor starts a timer and, past three seconds, shows a 'waking up server' banner and retries automatically, rather than leaving the interface looking frozen.
Read-only snapshots for shared links, not live access
Sharing a debt had to work without creating an account for the other person. A share link generates a token pointing at a snapshot of the debt at that moment, not a live reference, and it expires automatically after 48 hours via a MongoDB TTL index.
On-demand calculation as a safety net, alongside the cron job
A cron job flags overdue debts once a day, but the backend sleeps on Render's free tier and that cron job may not run if the server is idle at the time. As a safety net, getDebts() also recalculates overdue status on demand on every call, so even if the cron job fails, a read never returns a stale status.
Migrating from Karma to Vitest for zoneless compatibility
Zoneless requires explicit patterns that Karma didn't always handle well in this project: manual change detection in tests, specific imports like BrowserTestingModule and platformBrowserTesting. Migrating to Vitest resolved those compatibility problems and cut the suite's run time along the way.
No client, no revenue. Just a personal project
Zoneless, deliberately
Signals and zoneless change detection used as the primary state model instead of NgRx, not bolted on after the fact.
A migrated test suite
Karma replaced by Vitest once the project was already under way, to resolve real zoneless compatibility problems.
A complete system
Full JWT auth, password reset and bot protection, not a demo that skips the parts that don't show well.
Overdue status that doesn't rely on one mechanism
A daily cron job backed by on-demand calculation in getDebts(), so a sleeping server never leaves data stale.
This project has no conversion numbers to report, because it has no client. What it has is two public repositories anyone can look through and a full deployment. Always happy to take feedback!