From Complex Tech Stacks to Simple Solutions: My Journey Back to Basics
I've been around the block when it comes to tech stacks. I've shipped production code in Golang, built applications with Ruby on Rails, experimented with Elixir's actor model, and even contributed to open source projects. Hell, I once had to backport MongoDB driver support for Ruby to work with MongoDB 2.4's SCRAM authentication because the project demanded it.
All of that complex, impressive work? It stayed at the companies where I built it.
Today, I'm running my entire SaaS directory with Next.js and MongoDB. And honestly, my life has never been simpler.
The Complexity Trap We All Fall Into
As developers, we love shiny new technologies. We get excited about the latest programming language that promises better performance, or the new database that handles edge cases we might never encounter. I was no different.
During my time at various companies, I dove deep into:
- Golang for its concurrency and performance
- Ruby on Rails for rapid prototyping and development speed
- Elixir for fault-tolerant, distributed systems
- MongoDB internals (deep enough to modify drivers)
Each technology taught me something valuable. Golang showed me the beauty of simplicity in language design. Rails demonstrated how conventions can accelerate development. Elixir opened my mind to different approaches to building resilient systems.
But here's what I learned: complexity for complexity's sake is a productivity killer.
Why Rails Still Matters After 21 Years
Ruby on Rails launched in 2004. That's 21 years of refinement, battle-testing, and community wisdom. While working with Rails, I noticed something important: the framework had already solved most of the problems I was trying to solve with newer, "better" technologies.
Convention over configuration. Database migrations. Built-in testing frameworks. Asset pipeline. Authentication helpers. Form builders.
Rails didn't just give me tools. It gave me a philosophy: optimize for developer happiness and productivity, not for impressing other developers.
When I look at modern JavaScript frameworks like Next.js, I see Rails' DNA everywhere:
- File-based routing (remember Rails' RESTful routes?)
- Built-in API routes (Rails had this in 2004)
- Database integration patterns
- Environment-based configuration
- Development server with hot reloading
The difference? Next.js brings these proven patterns to the JavaScript ecosystem, where I can use one language for everything.
The Power of Constraint
Here's my current stack for building SaaS applications:
Frontend & Backend: Next.js with TypeScript and React
Database: MongoDB with Prisma
Search: Typesense (self-hosted)
Styling: Tailwind CSS
Deployment: Google Cloud Run with Terraform (though I also use Vercel for some projects)
That's it. Five main technologies, one language throughout the entire stack.
The React component model has become the standard way to think about user interfaces. After years of working with different templating systems and UI frameworks, React's declarative approach just makes sense. Components are predictable, testable, and reusable.
But the real game-changer is having TypeScript everywhere. Database queries, API routes, React components, utility functions - everything speaks the same language. No context switching between Ruby for the backend and JavaScript for the frontend. No mental overhead of remembering different syntax patterns.
Tailwind CSS completes this unified experience. Instead of maintaining separate CSS files or wrestling with CSS-in-JS solutions, I'm styling components directly in the markup with utility classes. It's fast, consistent, and when combined with React components, creates a development experience that just flows.
For deployment, I stick with Google Cloud Run and Terraform. Once you learn infrastructure tools and understand how they work, it's hard to give up that control and flexibility. Though I'll admit, Vercel's simplicity is tempting for smaller projects where I just want to push code and forget about it.
This constraint forces me to solve problems instead of researching solutions. When I had access to multiple programming languages and frameworks, I'd spend hours debating whether to use Postgres or MongoDB, whether to build the API in Golang or keep it in JavaScript, whether to try the latest state management library.
Now? I write TypeScript. I store data in MongoDB. I deploy to Vercel. Done.
What Simplicity Actually Looks Like
With my simple stack, I can:
- Build a complete feature from database schema to user interface in a single day, all in TypeScript
- Debug issues without context switching between languages or mental models
- Style components with Tailwind without leaving the JSX markup
- Onboard new team members without explaining six different technologies
- Deploy changes without coordinating multiple services
- Maintain applications without a DevOps team
The combination of React's component model, TypeScript's type safety, and Tailwind's utility-first approach creates a development experience where everything feels connected. I'm not jumping between a Ruby API, a JavaScript frontend, and separate CSS files. It's one cohesive workflow from data to user interface.
The MongoDB + Prisma combination particularly shines here. While I appreciate the structure that SQL databases provide, MongoDB's document model maps naturally to JavaScript objects, and Prisma provides excellent TypeScript integration without the complexity of traditional ORMs. No schema migrations for every small change. Just data that looks like the code that uses it, with full type safety.
One technique I've adopted is deliberately denormalizing data in MongoDB. This approach was inspired by my experience with DynamoDB, where denormalization isn't just common, it's essential for performance.
My current approach is a hybrid: I store data normalized for writes, but maintain denormalized collections optimized for reads. Yes, this means running small recalculation processes when data changes, but the performance gains are worth it.
For my SaaS directory, I use Prisma for the normalized write operations, then populate denormalized collections that are optimized for browsing. Since a directory doesn't need real-time updates (if an app gets a new review, it's fine if it shows up in search results a few minutes later), this eventual consistency works perfectly.
The beauty of this pattern is its simplicity to understand and maintain. When I'm debugging or adding features, the data flow is crystal clear. Even AI tools like Claude Code work better with this straightforward approach - they don't get confused by complex relationships or hallucinate crazy optimizations when the pattern is this explicit.
The recalculation overhead is minimal, and honestly, I can't help myself when it comes to these kinds of optimizations. Once you start thinking about query performance, it's hard to stop. But at least with this simple stack, my over-engineering tendencies are contained to data modeling instead of spreading across five different technologies.
The Hidden Cost of Technical Diversity
Let me give you a real example. I once built a POS (Point of Sale) system in Elixir. It was beautiful. Fault-tolerant, concurrent, handling thousands of transactions without breaking a sweat. The OTP supervisors meant that if one process crashed, the system would restart that component and keep running. Pure engineering elegance.
Today? Nobody can maintain it.
The system is too robust for its own good. It rarely breaks, which means the team never needed to learn Elixir deeply. When small changes are needed, they can't make them confidently. The few Elixir developers in our area cost 2x what JavaScript developers charge.
I'll eventually replace this bulletproof system with something built in a more common stack. Not because Elixir is bad, but because sustainability trumps technical excellence.
This pattern repeated everywhere I worked. The Golang microservices needed someone who understood Go's runtime. The Ruby applications required Rails expertise. When team members left, institutional knowledge walked out the door with them.
My simple Next.js applications? Any JavaScript developer can understand and extend them. The barrier to entry is lower. The maintenance burden is lighter. The long-term sustainability is higher.
Modern Development Leverages 21 Years of Lessons
The reason my simple stack works so well is that it stands on the shoulders of giants. Next.js didn't reinvent web development. It took the best ideas from Rails, Django, and other mature frameworks and adapted them for the JavaScript ecosystem.
MongoDB didn't create document databases from scratch. It learned from decades of database design and built something that works better for modern application development patterns.
TypeScript didn't ignore 30 years of type system research. It brought proven type safety concepts to the dynamic language developers were already using.
This is the secret: the best modern tools aren't revolutionary. They're evolutionary. They take battle-tested concepts and make them accessible in new contexts.
Looking Forward: Event-Driven Without the Chaos
I'm currently exploring an evolution of this simple stack: moving toward event-driven architecture using Next.js + Google Cloud + N8N. The idea is to maintain the simplicity I love while adding the benefits of asynchronous, loosely-coupled systems.
N8N handles the workflow orchestration, Google Cloud provides the infrastructure I already understand, and Next.js remains the familiar foundation. It's a natural progression that builds on what I already know rather than forcing me to learn entirely new paradigms.
This is how sustainable technology decisions work: small, deliberate steps that build on existing knowledge rather than dramatic rewrites that throw away years of learning.
When Simple Wins
I'm not saying complex architectures are always wrong. If you're building distributed systems for millions of users, you might need that complexity. If you're working with specialized domains like real-time financial trading or embedded systems, specialized tools make sense.
But for most SaaS applications, web apps, and startup MVPs? Simple wins.
My SaaS directory serves users and solves real problems. And I built it with technologies that any JavaScript developer can understand and maintain.
That's not just good business. That's sustainable development.
The Stack That Ships
After years of exploring different technologies, I've learned that the best stack is the one that gets your ideas into users' hands fastest. For me, that's Next.js and MongoDB.
Not because they're the most performant. Not because they're the most elegant. But because they let me focus on solving customer problems instead of solving technology problems.
And at the end of the day, customers don't care what's running on your servers. They care whether your application helps them get their job done.
My simple stack does exactly that.
Top comments (0)