Back to Blog

SaaS Codebase Review: Common Mistakes Founders Make

Published: June 30, 2026
SaaS Codebase Review: Common Mistakes Founders Make

There's a moment that exposes every shortcut at once: an enterprise prospect asks for a security audit before they sign, and you hand over the repo you were proud of. A few days later the report comes back with production secrets in your commit history, full-table scans freezing the database, and a UserService that's four thousand lines long. A SaaS codebase review is how you find those things before the auditor — or a 3am outage — finds them for you.

These eight come up in nearly every early-stage repo we look at. None are exotic; all are cheap to fix now and expensive to fix later. A defect caught in design is roughly 1x to fix and the same defect in production runs 60–100x more — the exact multiplier is genuinely disputed, but the direction isn't, and teams already burn 33–42% of their time servicing debt they shipped earlier (Stripe). So: the eight mistakes, and the fixes.

A SaaS codebase review finds the eight shortcuts an enterprise audit will flag

Mistake 1: Boolean Soft Deletes That Break Constraints

Founders add is_deleted BOOLEAN to avoid hard deletes — sensible — until a unique constraint meets it:

SQL
1ALTER TABLE users ADD CONSTRAINT unique_user_email UNIQUE (tenant_id, email);

A user deletes their account, tries to re-register with the same email, and the insert crashes — the "deleted" row still occupies the unique slot. The fix is a nullable timestamp plus a partial unique index:

SQL
1CREATE UNIQUE INDEX idx_users_active_email
2ON users (tenant_id, email)
3WHERE deleted_at IS NULL;

Now the constraint applies only to active rows, and re-registration just works.

Mistake 2: Secrets in Git History

Hardcode a database password or API key, commit it, and even if you delete it next commit, it's in your history forever. The fix going forward: environment variables, a gitignored .env, and a scanner like GitGuardian in CI to catch the next slip — exactly the discipline in our environment variable management guide. For one already leaked, a new commit isn't enough: rewrite history with git-filter-repo or BFG, force-push, and rotate the key, because you have to assume it's already been scraped.

Mistake 3: No Indexes Until Production Crashes

Everything's instant on a few hundred test rows, so missing indexes hide:

SQL
1-- O(N) full-table scan once this table is large.
2SELECT * FROM invoices WHERE tenant_id = 'company_abc' AND status = 'unpaid';

At hundreds of thousands of rows that scan locks up your database threads. Add a composite index matching how you query:

SQL
1CREATE INDEX idx_invoices_tenant_status ON invoices (tenant_id, status);

Index the columns you filter, join, and sort on — not every column — the same indexing discipline that turns a slow dashboard fast.

Missing indexes hide on small tables and surface as full-table scans in a SaaS codebase review

Mistake 4: God Services

Ship fast and unrelated logic piles into one file — a UserService doing auth, payments, emails, and metrics in four thousand lines. It violates single responsibility, and it's untestable, undebuggable, and unsplittable. Break domains into focused modules; a clean NestJS project structure is what keeps the God service from forming in the first place.

Mistake 5: No Immutable Audit Trail

Enterprise buyers need to know who changed what. Overwrite an invoice's status to "Paid" with no history and you can't answer "who authorized this, and when?" — a failing answer in a security review. Keep an append-only audit_logs table capturing the actor, timestamp, and before/after values on every critical state change. Enterprise buyers don't ask whether you have it; they ask to query it.

Mistake 6: Blocking the Event Loop

Render a PDF, optimize an image, or call a slow third-party API inside the request and you block Node's single thread — every other request hangs behind it until it finishes. Push heavy work to a background queue backed by Redis, return immediately, and notify when it's done. The request thread is for responding, not for grinding.

Mistake 7: No Error Telemetry

If you find out about production crashes from support tickets, you're flying blind. console.log to a rotating file doesn't cut it — a premium client's endpoint can fail silently for hours. Wire in Sentry (or similar) from day one to capture unhandled exceptions with stack traces and alert you before the ticket arrives.

Without telemetry, a silent production error is invisible until a customer files a ticket

Mistake 8: Weak Password Hashing

A surprising number of repos still hash passwords with MD5 or SHA-1 — fast algorithms, which is precisely wrong for passwords, and trivially cracked with modern hardware and rainbow tables. Use a slow, adaptive KDF: Argon2id (preferred) or bcrypt, with a sensible work factor, via a vetted library — never a hand-rolled hash. The OWASP Password Storage Cheat Sheet has the parameters, and our auth mistakes guide covers the rest of the boring-but-critical layer.

SaaS Codebase Review: The Checklist

LayerThe mistakeThe fix
DataBoolean soft deletedeleted_at + partial unique index
SecretsKeys in GitEnv vars + scanner + history purge
QueriesNo indexesComposite indexes on real patterns
StructureGod servicesFocused, single-responsibility modules
AuditOverwrite stateAppend-only audit log
ConcurrencyBlocking the loopBackground queue for heavy work
Observabilityconsole.log onlyReal error telemetry
AuthMD5/SHA-1Argon2id / bcrypt

None of these eight are hard to fix — they're hard to find once a codebase is large and shipping, which is exactly why you run the review before the audit does. Spend an afternoon on this list now and the enterprise security review becomes a formality instead of a fire drill. Cutting these corners doesn't save time; it borrows it at a brutal interest rate — and the bill always comes due on the deal you most wanted to close.

Frequently Asked Questions

A soft-deleted row still physically exists, so a UNIQUE constraint on email still counts it. When a user deletes their account and re-registers with the same email, the insert fails on the old 'deleted' row. The fix is a nullable deleted_at timestamp plus a partial unique index — UNIQUE (tenant_id, email) WHERE deleted_at IS NULL — so the constraint only applies to active rows and re-registration works.

A new commit deleting the key doesn't remove it — it's still in history forever. You have to rewrite history with git-filter-repo or BFG Repo-Cleaner, force-push, and then rotate the key, because you must assume it's already compromised. Prevent the next one by keeping secrets in environment variables, gitignoring .env, and adding a secret scanner like GitGuardian to CI.

No. Indexes speed up reads but tax every write, since the database has to update the index on each INSERT/UPDATE/DELETE. Index the columns you actually filter, join, or sort on — usually composite indexes matching your real query patterns, like (tenant_id, status). Indexing everything slows writes and wastes space without helping the queries you don't run.

Argon2id (preferred) or bcrypt — slow, adaptive key-derivation functions with a tunable work factor. Never MD5 or SHA-1: they're fast, which is exactly wrong for passwords, and trivially brute-forced with modern hardware and rainbow tables. Use the vetted library, never a hand-rolled hash, and follow the OWASP Password Storage guidance for parameters.

Before they ship. A defect caught in design costs roughly 1x to fix; the same defect in production runs far more — the long-cited figure is 60–100x, and while the exact multiplier is debated, the direction is solid. Teams already spend 33–42% of their time servicing technical debt (Stripe). Fixing these eight during a codebase review is the cheapest they'll ever be.

Portrait of Umar Farooq

About Umar Farooq

Umar Farooq is the founder and lead engineer of Codify SaaS. He builds B2B SaaS products and web applications on modern TypeScript stacks and enterprise Java, and writes code-first guides drawn from real production work — the schema decisions, the migrations that almost went wrong, and the performance fixes that actually moved the numbers. When he recommends an approach, he shows the code and explains the trade-offs.

Read full bio