The BuildFlow landing page, next to a project overview with contract value, progress, forecast and margin

The problem

A construction company runs several jobs at once. The budget is in one spreadsheet, the site photos are in someone's camera roll, and the client's yes or no is buried in an email thread.

The costly moments are the changes. A client asks for a different floor finish, a supplier's price moves, a wall hides a pipe nobody drew. Someone has to say yes or no, and they usually do it without seeing what it does to the budget.

I built BuildFlow around that moment. Everything else in the app, budgets, schedules and site reports, is there so the numbers behind a change order are current when someone has to decide.

Who uses it

Owner
A company-wide view: what needs attention today, receivables and cash flow, the team and the audit log.
Project manager
Runs a project day to day: tasks, milestones, timeline, budget, procurement and change orders.
Site manager
Works from a phone: daily site reports with photos, incidents and progress updates.
Accountant
Quotes, invoices, payments and expenses, with receivables aging.
Client
A portal limited to their own projects: progress, documents, and the change orders waiting for their approval.

What it does

  • Multi-tenant organizations with roles, member management and email invitations
  • A dashboard with a needs-attention list, receivables aging and expected collections
  • Tasks, milestones and a project timeline with a Today line
  • Budgets and expenses with actual, committed and forecast cost
  • Procurement with purchase orders and goods receipts
  • Quotes, invoices and payments
  • Change orders approved by the client through the portal, with an impact preview
  • Daily site reports, incidents and progress updates, with a phone-friendly quick report
  • Documents and photos in S3-compatible object storage
  • Notifications, an activity feed and a searchable audit log
  • PDF and CSV reports
  • Global search with a Command-K palette, limited to what each role can see
  • A role-based demo mode with a seeded showcase dataset

Where the engineering is

A change order shows its effect before anyone approves it

Open a pending change order and the impact panel lists the project's figures now, after approval, and the difference. It warns about problems, summarises what the change order earns on its own, and counts the other pending change orders so you can see their combined effect.

The API computes the preview, so the browser never guesses at money. The panel also states its one assumption in plain words: the forecast treats the change order's cost as not yet committed or spent.

A pending change order with the Impact of approval table showing figures now, after approval and the change, Open the full-size image (opens in a new tab)
The impact panel on a pending change order.

A site report you can finish with one hand

A site manager standing in a half-finished room has one free hand and an unreliable signal. The quick report is four short steps: the basics, the work done, photos, and a review before sending.

It saves a draft on the phone as you type, shrinks photos in the browser before uploading them, and says so when the phone goes offline. Nothing is sent until you choose Send report.

The BuildFlow project overview on a phone, with the needs attention list, Open the full-size image (opens in a new tab)
The app on a phone.

Finding things quickly in a big app

A project piles up tasks, invoices, reports and people. Command-K opens a search across the app, the dashboard lists what needs attention first, the timeline shows a project's schedule on one chart, and the audit log records who changed what.

The audit log table listing who changed what and when, Open the full-size image (opens in a new tab)
The audit log, filtered by project, person, action and date.

Separate organizations, and a safer default

Each company's data is kept apart: requests name an organization and the server checks that the signed-in user belongs to it. Sessions use opaque cookies rather than tokens kept in browser storage, state-changing requests are protected against CSRF, logins are rate limited, request bodies have size limits, and responses carry the standard security headers.

Integration tests run these rules against a real database, so a change that weakens one fails the test run.

Screens

How it's built

LayerTechnology
BackendJava and Spring Boot (Web, Security, Data JPA, Validation, Actuator, Mail), Flyway, PDFBox
DatabasePostgreSQL 17
StorageMinIO, for documents and photos
FrontendNext.js 16, React 19, TypeScript, CSS Modules
TestingJUnit with Testcontainers, Vitest with Testing Library
InfrastructureDocker Compose, with Caddy in front in production
BuildFlow architecture: Next.js talks to the Spring Boot API over REST with an HTTP-only session, and the API uses PostgreSQL, MinIO and Mailpit, Open the full-size image (opens in a new tab)
Request and data flow, from Next.js to Spring Boot, PostgreSQL, MinIO and Mailpit.

Testing

The schema grows through 13 Flyway migrations, so every environment reaches the same state the same way.

More than 100 backend tests run against a real PostgreSQL started by Testcontainers, not a mock. The frontend has Vitest and Testing Library tests for its helpers, components and pages.

One seed-data test failed now and then because two steps raced each other. I made the order explicit and ran it repeatedly until it stayed green.

What I would build next

  • Finish a site report with no signal at all, and sync it when the phone reconnects.
  • Add a signature step to the client's approval of a change order.
  • Run an accessibility pass with a screen reader over the main flows.

Built with

  • Java
  • Spring Boot
  • PostgreSQL
  • MinIO
  • Next.js
  • React
  • TypeScript
  • Docker

56 results