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.
Top comments (31)
no serverless? how do you expect this to scale???
Now you are just trolling :)
he's a friend, trolling is required by law :D
Neat, less mess means more time for building fun stuff instead of fixing problems all day, but it's pretty good. But does keeping things simple always work when your app grows and gets bigger challenges?
I'd say so! Mostly because you can continue to vertically scale for a very very long time and that gives you time to start planning.
I can't agree more with you. Many modern patterns are overkill for most projects and produce unnecessary complexity (and opportunities for things to break).
As I get older I appreciate simple over "clever". You can write a monolith in a way that can be decomposed in the future, if needed. But cross that bridge when you come to it.
I was procrastinating on a personal project because of this. I've only focused on frontend development for the past years, and I was struggling with choosing the stack. By reading this post, you motivate me to stop overthinking about setting up all the perfect tools and just start. Great post!
are you just using express and typescript or something else ?
I'm using Fastify with typescript. I know you didn't ask me. But there you have it.
I'm still interested in the answer of Shayan.
Yes still using express and trpc
This is spot on. I've seen too many teams overcomplicate their architecture way before they have product-market fit. TypeScript + Postgres is a killer combo—fast to build, easy to maintain, and flexible enough for 95% of use cases. Love the reminder that users > infrastructure debates. Build the thing first, then worry about scale.
I'm a strong believer in the NAP stack - NestJS, Angular, and PostgreSQL. Still only TS and pSQL and a bit more boilerplate than your lean setup but it scales very easily and having both front and back end follow similar architecture patterns is huge when bringing new coders in.
Could you clarify what you mean by
LISTEN/NOTIFY
? Is that a feature in postgres?LISTEN & NOTIFY events are supported in Postgres: postgresql.org/docs/16/sql-listen.... - They're postgresql's pub/sub system. It can do everything from respond to record changes (say a job state change, or a declined credit card transaction), INSERT notifications, sending messages between DB clients, etc.
But there are some important considerations:
I use node's pg_listen module running on a dedicated docker container with a single non-pooled connection, and then something like NATS or gRPC to broker messages as needed.
Bonus reading: look into the pg_cron postgres extension for scheduling jobs in postgres directly:
Combined with LISTEN/NOTIFY, it's super powerful.
Thanks for all the valuable insights!
Well we are using kotlin with PostgreSQL, and on jvm at least we had to use a non maintained PostgreSQL specific connector without pooling to use listen notify. It was a few year's ago but are there better connectors now a days? Also listen notify has an 8k transfer limit afaik?
Having said that I like PostgreSQL a lot and always say, let's prove that our system is actually reaching the limits before adding more complexity using maybe load tests so I agree with the essence of the article.
F* great article!
Most startups and scale ups should just follow what you are suggesting
Great article. I love "You’re Not Google. Scaling Isn’t Your Problem (Yet)". You are absolutely right my friend.
Give yourselves some space to breath dear devs.
Thank you :)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.