Every legacy system tells on itself once you actually read the database. VouchFor — a referral-based job platform originally built as a WebMatrix-era ASP.NET "Web Pages" app on .NET 4.0 — is a good example of why "just rebuild it in something modern" is the easy part of a rewrite, and reading the old system honestly is the hard part.
We're modernizing VouchFor onto Next.js, PostgreSQL via Prisma, and NextAuth. Before writing the new schema, we reverse-engineered the legacy SQL Server Compact database and the app's own code — and what we found shaped almost every decision in the rewrite.
What the platform does
VouchFor connects three kinds of users — Contractors looking for work, Employers posting jobs, and Referrers who vouch for people in their network — around a simple idea: a referral from someone who actually knows the candidate is worth more than a cold application. Referrers build a list of people they'd vouch for, attach an endorsement, and refer them against open jobs.
Reading the old database first
The legacy schema had one real foreign key constraint in the entire database — between Team and UserProfile. Everything else, including the relationships tying Jobs and Referrals together, was only ever enforced in application code, not the database itself. That's a real data-integrity gap: nothing stopped an orphaned or mismatched record from being written if the application logic missed a case.
We also found data that had been quietly corrupting for years: the legacy Jobs.Status column contained values like "Openrred" and "Openrredred" — the fingerprint of a string-concatenation bug that had been silently mangling job statuses in production. And two overlapping "team" concepts existed side by side — a TeamID column sitting directly on the user profile, and a separate Team table doing something similar but not identical.
The security issue that mattered most
The most important finding wasn't a data quality issue — it was a security one. The legacy job-editing page read a job's ID straight out of a hidden form field, with no server-side check that the logged-in user actually owned that job: a classic IDOR (insecure direct object reference) vulnerability. Anyone who could guess or view another user's job ID could potentially act on a record that wasn't theirs.
The rewrite fixes this properly rather than papering over it: every mutation now re-derives the owner from the authenticated session, never from an ID or ownership value the client sends. Switching to opaque, non-sequential IDs (cuid()) closes off casual enumeration as a second layer of defense, but the real fix is the ownership check itself — and that's the detail an ID format alone would never have caught.
What we carried forward, what we fixed, and what we flagged
- Consolidated where the legacy data was simply inconsistent — two separate, inconsistently-used
CompanyandCompanyNamecolumns became one cleancompanyNamefield. - Kept but flagged fields that existed but were never actually used — Address, ABN, BAS registration status and a biography field existed in the legacy schema but were never read or written by any page in the codebase we were given. Rather than silently dropping them (and the data behind them), we kept them as optional fields and flagged them as candidates to remove — a decision for the business to confirm, not one to make unilaterally.
- Replaced legacy auth wholesale — the old ASP.NET SimpleMembership scaffolding and DotNetOpenAuth social login are replaced by NextAuth's standard Account/Session/VerificationToken model, a well-maintained, modern equivalent rather than a bespoke reimplementation.
- Added the referential integrity the original never had — every relationship in the new schema is a real, enforced foreign key.
The takeaway
A legacy rewrite done well isn't "port the old screens to a new framework." It's an audit first — read the real data, find the bugs the original team never got to, decide deliberately what to keep, fix, or drop — and only then design the schema that the new application deserves. The tech stack is the easy 20%; understanding what actually happened in the system you're replacing is the other 80%.
Sitting on a Legacy System Nobody Wants to Touch?
We audit before we rebuild — so the new system fixes the real problems in the old one instead of just repainting them.
Get in Touch