All workCase study

MyDebts

Personal project

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

CLIENT
Personal project
SECTOR
Personal finance
ROLE
Everything
TIMELINE
In development
MyDebts homepage
01 / THE PROBLEM

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.

THE CONSTRAINTS THAT SHAPED EVERYTHING
  • 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.
02 / WHAT I BUILT

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.

03 / DECISIONS

Five decisions, and what each one cost.

01

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.

TRADE-OFFFewer examples to lean on when something didn't behave as documented or a bug came up. Zoneless is newer, so most Stack Overflow answers still assume zone.js.
02

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.

TRADE-OFFThe banner is a visible reminder that this runs on free-tier infrastructure, not with the uptime of a professional environment. I'd rather show that honestly than hide it behind a spinner that looks identical whether the request resolves in 200ms or 8 seconds.
03

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.

TRADE-OFFThe other person can't update or edit the record through the link; any correction has to go through whoever owns the debt. For an informal, two-person use case, that's a feature, not a gap.
04

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.

TRADE-OFFHaving two mechanisms computing the same thing adds a little redundancy, but on a backend with intermittent availability that's deliberate redundancy, not accidental duplication.
05

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.

TRADE-OFFRewriting the testing setup costs time that never shows up in the finished feature set, but it was necessary to make the suite more reliable.
04 / RESULT

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!

NEXT CASE STUDY

Cofers.mx

A homepage redesign and a new page, built from the client's Figma designs and adapted for mobile, along with a full performance optimization.

Read more