The most expensive mistake in software is not building the wrong MVP. It is building the right MVP in a way that structurally prevents you from scaling it. The 'rewrite tax' — paying engineers to rebuild what already works because the foundation cannot hold more load — costs companies six months of runway at exactly the worst time, usually right after a successful funding round.
After auditing, rescuing, and rebuilding dozens of startup codebases, we have identified the core architectural decisions that consistently determine whether a product scales smoothly or hits a brick wall.
1. The Database: Pick Postgres and Master It
The decision between Postgres, MongoDB, and a NoSQL database is not about which has the most modern marketing page. It is about your data access patterns and relational integrity.
Startups often choose MongoDB for 'flexibility' early on, only to realise a year later that their data is highly relational. They end up writing complex application-level joins, resulting in massive N+1 query problems and severe performance degradation.
- —Default to PostgreSQL. It is the most robust, extensible database available.
- —Need flexibility? Use Postgres JSONB columns. You get schema-less flexibility combined with robust indexing and relational joins.
- —Need search? Postgres full-text search or pgvector (for AI) is sufficient for 95% of use cases before you need Elasticsearch or Pinecone.
- —Do not introduce a secondary data store (like Redis for caching, or Mongo for logs) until the specific read/write load on Postgres dictates it.
2. The API Contract: Schema Validation is Mandatory
Every breaking API change downstream costs disproportionate time: client updates, backwards-compatibility layers, documentation, coordinated releases. If your backend accepts arbitrary JSON payloads without strict validation, you are accumulating massive technical debt.
Design your API contract on day one as if you will never be able to break it. Use strict schema validation at the edge.
// Using Zod in a Next.js / Node environment for strict API boundaries
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
company_name: z.string().min(2).max(100),
role: z.enum(['admin', 'member']).default('member'),
preferences: z.record(z.string(), z.boolean()).optional(),
});
export async function POST(req: Request) {
const body = await req.json();
// This throws a clear, typed error if the payload is invalid.
// Malformed data never reaches your core business logic or database.
const parsedData = CreateUserSchema.parse(body);
return createUser(parsedData);
}3. Multi-tenancy: The Foundation of B2B SaaS
If your product will ever serve multiple customers (tenants) with data isolation requirements, your database schema and API layer must be multi-tenant from the very first commit. Data leakage across tenants is not a bug you can hotfix; it is a critical incident that breaches contracts.
Do not attempt to bolt multi-tenancy on later. Ensure every single table (except global configuration) has a `tenant_id` (or `organization_id`) foreign key, and ensure your ORM or query builder enforces this filter globally.
// Example of a global tenant scope in Prisma / ORM
// Never write queries that don't include the active tenant context.
async function getProjects(tenantId: string, userId: string) {
return await db.project.findMany({
where: {
tenant_id: tenantId, // CRITICAL: Always scope by tenant
OR: [
{ visibility: 'public' },
{ owner_id: userId }
]
}
});
}4. The Queue/Worker Split: Async from Day One
Any operation that takes more than 300ms should not be in your synchronous API request path. Webhooks, email sending, PDF generation, AI model calls, and heavy aggregations must be handed to a background queue.
Building a monolithic API where user requests block while an external API responds is the fastest way to bring down your servers under moderate load. Introduce a message queue (BullMQ, SQS, or even Postgres-based queues like Graphile Worker) early. Architect your system so the API returns a `202 Accepted` and a `job_id`, and the client polls or listens for completion.
5. Observability: Instrumentation is not an Afterthought
Structured logging (JSON, not plain text strings), distributed tracing, and error alerting cost almost nothing to add at project inception. They cost an enormous amount to retrofit across a codebase with thousands of files.
"None of these are premature optimisations. They are foundational decisions that cost hours to implement at inception, and months to retrofit in production. The question is not whether to make these decisions — they will be made for you by default if you don't. The question is whether you make them deliberately."