Skip to content

Engineering

Boring Stack Decisions That Age Well for Startups

Why PostgreSQL, server-rendered components, and single monoliths outlast microservices and bleeding-edge frameworks for early software products.

Dhanji Bhagat

Dhanji Bhagat

Founder & Principal Engineer

6 min read
architecturePostgreSQLtech stackMVP

The most expensive mistake an early-stage startup can make is confusing architectural complexity with engineering quality.

Founders and technical leads often pick infrastructure based on what large technology companies use to serve twenty million requests per minute. They adopt microservices, Kubernetes clusters, distributed message brokers, and complex client-side state managers before acquiring their tenth customer.

Six months later, the team spends half their engineering hours fixing hydration mismatches, managing multi-repo package versions, and debugging network partitions between five internal services. Meanwhile, the actual product roadmap sits frozen.

When we build software at Emiote, we optimize for durability, debuggability, and low operational overhead. Here are the five architectural decisions that consistently survive five years without requiring an emergency rewrite.


The five-year durability matrix

Technology choices carry two costs: the initial implementation cost and the recurring maintenance tax.

Bleeding-edge setups promise quick initial development through heavy abstraction, but they demand continuous maintenance as frameworks change APIs and dependencies break. Boring technologies require explicit initial setup, then run quietly for years.

flowchart TD
    subgraph Durable["Durable Boring Stack (Under $20/mo, Single Node)"]
        BrowserA["Browser Client"] --> AppNode["Monolith App Server<br/>(SSR + Islands)"]
        AppNode --> PG[("PostgreSQL Instance<br/>- Row-Level Security<br/>- SKIP LOCKED Queues<br/>- ACID Transactions")]
    end

    subgraph Fragile["Fragile Novelty Stack (High Maintenance Tax)"]
        BrowserB["Client SPA Bundle"] --> Gateway["API Gateway"]
        Gateway --> AuthSvc["Auth Service"]
        Gateway --> BillSvc["Billing Service"]
        Gateway --> CoreSvc["Core Service"]
        AuthSvc --> AuthDB[("Auth DB")]
        BillSvc --> BillDB[("Billing DB")]
        CoreSvc --> CoreDB[("Core DB")]
        AuthSvc & BillSvc & CoreSvc <--> Bus{"Kafka Event Bus"}
    end
Problem DomainNovel Choice (High Tax)Boring Choice (Durable)What Happens in Year Three
Primary DatabaseMicroservices with separate databasesSingle PostgreSQL with Row-Level SecuritySingle transactions guarantee balance integrity without distributed locks
Code OrganizationPolyrepo across six separate repositoriesSingle monolithic repositoryRefactors take minutes with typed imports instead of coordinated npm releases
User InterfaceClient-side SPA with heavy client stateServer-rendered HTML with lightweight islandsZero hydration errors and search engines index every page instantly
Background JobsDistributed event streaming (Kafka/RabbitMQ)PostgreSQL advisory locks or Redis queuesZero queue cluster maintenance; queries run directly against main data
Hosting & OpsMulti-node Kubernetes on managed cloudSingle virtual private server or LightsailInfrastructure costs stay under $20 monthly with minimal monitoring overhead

1. Single PostgreSQL database with Row-Level Security

Dividing an early product into separate database instances per tenant or microservice introduces immediate distributed systems problems.

You lose cross-table foreign key constraints. You lose single-statement atomic transactions. Generating a simple customer report requires network joins across HTTP endpoints or eventual consistency synchronization workers that silently drift out of alignment.

PostgreSQL handles structured relational data, JSON documents, full-text search, and geographic coordinates within a single engine.

For multi-tenant applications like our accounting software Ankik, PostgreSQL Row-Level Security (RLS) isolates tenant data at the query engine layer:

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_policy ON invoices
    FOR ALL
    USING (organization_id = current_setting('app.current_org_id')::uuid);

Every query automatically filters by tenant, preventing data leaks across organizations even if application code forgets an explicit where clause. A single PostgreSQL instance on an inexpensive virtual server easily handles millions of records and dozens of concurrent queries at sub-50ms latencies.


2. Monolithic repository over polyrepo sprawl

Splitting an early application across separate git repositories creates immediate organizational drag.

Updating an API payload schema requires editing the backend repository, running a build script, tagging a package version, publishing to an internal registry, updating the frontend repository, and resolving version conflicts across pull requests. Small changes that should take ten minutes require half a day of dependency coordination.

A single repository containing the server, frontend, and shared type definitions keeps dependencies aligned.

With TypeScript, a backend schema change immediately produces compile-time warnings across any frontend component using that model:

// packages/shared/src/schemas/invoice.ts
export interface InvoiceItem {
  description: string;
  quantity: number;
  unitPriceCents: number;
}

Both client and server import the exact same contract. Refactoring an API field requires one find-and-replace operation across the entire codebase.


3. Server-rendered HTML with lightweight interactive islands

Single-page applications (SPAs) that render entirely in the client browser shift enormous complexity onto the user’s device.

The client must download several megabytes of JavaScript, execute bundle parsing, render a blank loading spinner, call four API endpoints in parallel, manage client cache invalidation, and reconcile state updates. When a network connection stutters, users see partial white screens and frozen buttons.

Server-rendered pages with progressive islands flip this dynamic.

The server generates standard HTML and CSS, sending complete documents that render immediately on slow mobile connections. Interactive islands (such as a complex dropdown or a dynamic calculation widget) hydrate only when required.

Benefits of this approach:

  • Initial page load times drop to fractions of a second.
  • Search engines and AI scrapers index content without running expensive headless browser emulation.
  • Client state bugs disappear because the server remains the single authoritative source of truth.

4. PostgreSQL advisory locks and BullMQ over Kafka

Distributed event streaming platforms like Apache Kafka or RabbitMQ are built for engineering organizations with hundreds of developers processing terabytes of log streams.

Using Kafka in an early-stage startup adds cluster coordination services, consumer group rebalancing bugs, and deployment overhead before the product has meaningful background volume.

Most background tasks in early products fall into three categories: sending transactional emails, generating PDF invoices, and processing webhook payloads.

PostgreSQL handles background queues natively using FOR UPDATE SKIP LOCKED:

WITH next_job AS (
    SELECT id FROM background_jobs
    WHERE status = 'pending' AND run_at <= NOW()
    ORDER BY run_at ASC
    LIMIT 1
    FOR UPDATE SKIP LOCKED
)
UPDATE background_jobs
SET status = 'processing', locked_at = NOW()
WHERE id IN (SELECT id FROM next_job)
RETURNING *;

This pattern avoids external message broker dependencies entirely. The job queue lives in the same transaction as the application state, eliminating partial writes where a record is saved to the database but the message broker call fails. When background volume eventually grows into thousands of jobs per minute, migrating to a simple Redis-backed queue like BullMQ takes less than one engineering day.


5. Single VPS deployment over Kubernetes

Container orchestration platforms like Kubernetes add layers of networking abstraction, ingress controllers, volume claims, and manifest files that demand specialized DevOps maintenance.

For products with under ten thousand active users, a single virtual private server (such as AWS Lightsail or Hetzner) running Docker with automated backup scripts is reliable, predictable, and simple to debug.

Real operational facts from Ankik:

  • Runs on a single instance with managed PostgreSQL backups.
  • Total hosting cost remains under $10 monthly.
  • Server CPU utilization sits below 8% under typical business daily workloads.
  • Debugging an error requires SSH access and reading a single unified container log file with docker logs, without searching through distributed pod logs.

When you control your deployment directly, you can diagnose performance bottlenecks in seconds instead of navigating cloud provider management dashboards.


Choosing predictability over novelty

Engineers often pick novel tools because solving infrastructure puzzles feels engaging. But customers do not pay for your build pipeline or your service mesh. They pay for reliable software that finishes their work without unexpected downtime.

Boring technology allows a small team of two engineers to build and maintain what previously required a team of ten. It keeps monthly operating expenses minimal and frees founders to focus on user feedback, product refinement, and distribution.

If you want a technical evaluation of your current architecture or want to simplify an overgrown tech stack, read our infrastructure migration post or schedule a Stack Review with our studio.

BOOK A CALL

Ready to turn your idea into a live product?

Schedule a 15-minute scoping call with Dhanji below. We'll discuss your scope, timeline, and tech strategy honestly.