Build Budget
Track a house build: stages, expenses against budget, documents and bank-ready PDF reports.
- Role
- Everything: product, API, frontend, infrastructure, CI
- Stack
- Next.js · NestJS · PostgreSQL · Redis
- Hosting
- Vercel (web) · Railway (API)
- Links
- Live demo ↗GitHub ↗Code is public
The problem
I'm building my own house, and a build produces a lot of information: contractors' phone numbers, photos from the site, receipts, invoices and permits. Each stage has its own pile, and in practice it ended up scattered across spreadsheets, a phone gallery and paper folders. I wanted one place to store all of it, organised by stage and next to the budget, so I can find anything in seconds and share it with everyone involved.
My role
Everything, end to end: product scope, the NestJS API, the Next.js frontend, infrastructure and CI. I built it as a complete SaaS product rather than a CRUD demo, with proper auth, payments, background jobs, file storage and real-time updates.
What I built
Budget per stage
The build is split into stages, and each stage has its own page with its expenses, documents, diary and tasks. Expenses are tracked against the planned budget, with a summary chart and a warning on every stage that goes over.
Documents and construction diary
Receipts, invoices and permits go straight from the browser to S3 through presigned URLs, so the API never handles the bytes. Each stage has a diary with photos from the site.
Bank-ready PDF reports
Reports are generated in the background with Redis, BullMQ and PDFKit, so a heavy report never blocks a request. The user downloads it when it's ready.
Live collaboration
A project can be shared with owner and editor roles. Socket.io pushes stage and expense changes to everyone who has the project open.
Subscriptions
Stripe Checkout with a free plan limited to one project. The plan status changes only through signature-verified webhooks.
Reminders and cost benchmark
A daily job e-mails reminders for tasks due within three days. A nightly job computes anonymised cost per m² by region and stage, cached in Redis and shown on a public page.
Engineering
- Monorepo: Next.js (App Router) web app, NestJS API and a shared package, so the password policy is defined once for both.
- JWT access and refresh tokens in httpOnly cookies set by Next.js route handlers. An authenticated proxy refreshes the session silently, and e-mail verification is required before the first login.
- Unit tests with 80% coverage thresholds on logic-heavy files, plus end-to-end tests that boot the real app against a separate database. CI runs both and builds both apps on every push, with Postgres and Redis as service containers.
- Multi-stage Docker image on node:20-slim rather than Alpine, because Prisma's query engine needs glibc and OpenSSL. The image contains only the API workspace.
- E-mail goes through Mailpit locally and Resend's HTTPS API in production, because Railway blocks outbound SMTP.
- Bilingual UI (PL/EN) with next-intl.
Key decision
Fixing a refresh-token race condition
Every panel on the project page fetches its own data, so when the access token expires several requests try to refresh it at the same moment. With rotation on every refresh, the first request to land invalidated the token the others were still holding, and the user was logged out mid-session. I made refresh idempotent: the refresh token is rotated only when a new session starts (login or e-mail verification), so parallel refreshes all succeed against the same token.