All posts
PostgresArchitectureOpinion

Why I keep reaching for boring tech

Published December 15, 2025 · 4 min read

I want to be clear about what I'm not saying

I'm not saying new tech is bad or that you should never try anything. I've used newer databases, played with edge runtimes, experimented with ORMs that promise to make everything easier. Some of it's genuinely interesting.

But after several years of shipping real products — BookUp, the InterRail ERP, projects for clients — I keep landing in the same place: Postgres, NestJS, TypeScript, Redis for caching and queues. The same stack, more or less.

Here's why I'm at peace with that.

Postgres does everything

I mean this literally. Postgres handles:

  • Relational data with foreign keys and joins (obviously)
  • JSON and JSONB columns when you need a flexible schema
  • Full-text search that's good enough for most applications
  • Geospatial queries via PostGIS
  • Time-series patterns via TimescaleDB
  • Row-level security for multi-tenancy
  • Triggers and stored procedures when you need them

Every time I've thought "maybe I need a separate database for X", the honest answer has been "no, Postgres can do X." The cost of operating one database is much lower than operating three. And Postgres has been around long enough that the gotchas are well-documented.

For BookUp's multi-tenant billing, the wallet ledger, the scheduling system — all Postgres. No second thoughts.

NestJS is opinionated in the right ways

I've built APIs in Express, Fastify, Hono, and NestJS. Express and Fastify are great for small things. As projects grow, you end up building your own conventions — dependency injection, module structure, guard/middleware patterns.

NestJS has all of that built in. It's heavier, but when I'm joining a project or bringing someone else onto one, the conventions are already there. The DI system means I can test service logic without spinning up a database. The decorator-based guards mean auth is impossible to forget on a controller.

The criticism is that it's "too Angular-y" or "too enterprise." I don't disagree with that framing, honestly. But I'm not building toy apps.

The reliability argument

Here's the thing about boring tech: it fails in boring ways. When Postgres has a problem, there's a StackOverflow answer. When a new database has a problem, you're reading the GitHub issues from three weeks ago trying to figure out if it's a known bug.

I've been burned twice by choosing cutting-edge persistence layers early in projects. Both times, I spent more time working around limitations than building features. Both times, I ended up migrating to Postgres anyway.

The hype cycle is real. Technologies that are "the future of databases" in year one often have caveats that only appear in year two when your data grows.

When I do reach for something different

There are times I reach outside the boring stack. Queues: I use BullMQ on Redis because job processing genuinely needs it. Real-time: WebSockets when needed (and I built noServer to understand the protocol). Search: if the app's primary use case is search, Elasticsearch or Typesense makes sense. LLM integration: obviously not Postgres.

The pattern: I add something new when there's a specific need that the boring stack genuinely can't handle well. Not because it's interesting, not because it's on the trending repos list.

Boring isn't the same as simple. Postgres is sophisticated. NestJS is complex. But they're battle-tested, and their complexity is documented. That matters more than novelty when you're shipping real things.