My Backend Stack Is Just TypeScript + Postgres. Here’s Why That’s Enough
Most people overthink their backend way too early. You start building a new product and suddenly you’re researching Kafka, Redis, background workers, message queues, analytics pipelines, caching layers, and five microservices. But if you're being honest, you probably don’t need most of that.
For a huge number of SaaS products, especially early-stage ones, a simple stack will get you much further, much faster. My entire backend stack is just TypeScript and Postgres—and that’s been more than enough.
Here’s how I built UserJot on this stack and why I'm sticking with it.
TypeScript: One Language to Rule the Stack
Using TypeScript everywhere means less context switching. I don’t need to bounce between Python for one service, Go for another, and JavaScript for the frontend. One language handles everything—from writing API logic to validating data to shaping types across the app.
On the backend, TypeScript is surprisingly good. With tools like tRPC
and Zod
, you can build fast, fully type-safe APIs without even writing a separate schema or REST contract. You validate inputs once, infer your types across the app, and keep everything in sync.
It’s also much easier to onboard new contributors. If they already know frontend TypeScript, they’ll pick up the backend quickly. You don’t need to teach them the ins and outs of three different languages or frameworks.
Postgres: A Powerful Database That Does It All
People love to complicate their data stack. But honestly, Postgres is a beast. It stores relational data like a champ, handles JSON if you need some flexibility, supports full-text search, and has excellent support for indexes and constraints.
Need background jobs? You can use LISTEN/NOTIFY
, scheduled triggers, or even poll a table in a separate worker process. Want to store app events, audit logs, or analytics? Postgres can do that too.
Modern hardware makes this even more powerful. A single vertically scaled Postgres instance on today’s cloud infrastructure can have 64+ vCPUs and 256+ GB of RAM. That’s more than enough for the majority of SaaS products—by a long shot. Realistically, 99% of apps will never come close to saturating a machine like that.
And here’s the kicker: monthly active users are not concurrent users. If you have 100,000 MAUs, that doesn't mean you're serving 100,000 requests at once. Most users log in for a few minutes a day, some even less. Your concurrency is a tiny fraction of your MAUs. You’d need millions of active users to even start pushing the limits of a well-optimized Postgres instance.
If you get to that point, that’s a great problem to have—and you’ll have the resources and time to solve it properly. Premature scaling isn’t just unnecessary, it usually leads to worse decisions and more brittle systems.
Fewer Moving Parts = More Focus
The fewer tools you bring in, the fewer things you have to maintain. Each new service adds surface area: config files, deployments, edge cases, monitoring, failure recovery. If something breaks, you want to know where to look. When your stack is just two things, it’s a lot easier to debug.
It also means your local dev environment is simple. You don’t need Docker Compose running 12 containers. You don’t need separate services for background jobs, for caching, or for inter-service communication. Just boot up your Node server and a Postgres container and you're ready to go.
I’ve built complex systems before—Kafka clusters, ClickHouse analytics pipelines, Redis-backed job queues. They all have their place. But they also have a cost. And in the early days of a product, they’re almost always unnecessary.
Simple Software Scales Better
It’s counterintuitive, but the simpler your stack, the easier it is to scale when you actually need to. Most people assume they need to optimize early so they don’t hit scaling limits later—but it’s usually the opposite. Premature optimization locks you into decisions that are hard to undo. It creates complexity that slows you down and makes scaling harder, not easier.
Scaling a straightforward app built on Postgres and TypeScript is orders of magnitude easier than trying to scale a Frankenstein’s monster of services that were added “just in case.” The best way to be ready for growth is to keep things clean until you know what actually needs to scale.
You’re Not Google. Scaling Isn’t Your Problem (Yet)
Most apps don’t need to scale. They need to survive long enough to get users. It’s easy to convince yourself that you’re building for scale, but what you’re often doing is wasting time solving problems you don’t have.
Postgres can handle thousands of writes per second. It can store millions of rows without breaking a sweat. Vertical scaling gets you surprisingly far—one beefy Postgres box will outlast your early scaling needs. And once you hit a real bottleneck, you’ll know exactly what you need to improve.
Optimizing too early is a form of procrastination. You can ship features or you can spend two weeks writing the perfect Redis caching layer for a homepage that nobody’s visiting. I’ve done both. The first one wins.
Focus = Speed = Better Product
The best thing about a simple stack is how fast it lets you move. You don’t need to spend time gluing services together or reading 20 pages of docs for a tool you barely understand. You just build.
CI/CD gets easier. Testing gets faster. You have fewer libraries to keep updated. When something breaks in production, there are fewer places to look. All of that adds up to more time spent actually improving your product.
For solo devs or small teams, this is a huge deal. You can get more done with less code, fewer bugs, and less context switching. You’re not wasting your energy managing complexity—you’re building features users actually care about.
But What About Background Jobs, Caching, and All That?
There are always going to be edge cases where you could reach for a more complex tool. But even then, TypeScript and Postgres can usually take you surprisingly far.
Background jobs? Set up a cron worker that checks for jobs in a table and marks them complete. Need to react to events? Use LISTEN/NOTIFY
and a lightweight event dispatcher in your backend.
Caching? Sure, you can always layer on a simple in-memory cache for specific endpoints or even just use HTTP-level caching. For most SaaS apps, it’s enough.
Analytics? Log events into a Postgres table, aggregate them on a schedule, and display them in a dashboard. Unless you’re pushing huge volumes of real-time data, this is plenty.
The truth is that you can always add more later—but it’s really hard to take things out once they’re baked into your architecture.
Final Thoughts
I built UserJot, a user feedback tool, on this exact stack: just TypeScript and Postgres. It handles signups, feedback submissions, full-text search, API requests, cron jobs, background workers, rate limits, and more—all without needing to introduce any other services.
If you’re building a SaaS or an internal tool, don’t overthink it. The stack doesn’t have to be flashy. It just needs to be reliable and fast to build on. TypeScript and Postgres will take you way further than most people think.
Keep things simple. Move faster. And when it’s finally time to scale—you’ll be glad you started with something clean.