choosing a stack for fun and profit
· 7min · development
vibe check
Think about how many times you get to start a project from true zero. Now, how many of those currently have end users?
If you’re not the founder of a company, it’s probably a number in the single digits, and this gnaws at me when I think about my own career.
For developers in an organization that experiments regularly with new tech, or on a team undertaking the quest for microservices, you may have been able to spin up something fresh a few times or iterated on a boilerplate starter.
Other than those rare opportunities, the only way to practice new project setup is through tutorials or guides, a hundred new fake ecom stores and a thousand todo list apps.
Even with all the above, my number is under ten.
Starting today, I’m going to spin up a microSaaS product and I’m going to talk about the process as i do it. I want to build something with the expectation that at least one user will have it in their hands by the end, and it won’t be built in a weekend.
I don’t want to be yet another forgotten tutorial, but instead a conscious discussion on what decisions i make, the tradeoffs therein, and some thoughts on where there be dragons in the future.
why wouldn’t they pick [my favorite language]?
This is probably the most common question that engineers like to talk about over coffee. I won’t get into details here because we’re all an opinionated bunch, but every role I’ve had at every job paying my salary with that “awful” tech stack has at least one discussion about this. I’m not afraid to admit that I started my fair share of them.
The answer is deceptively simple. The first person or team chose a stack that they liked, were proficient in, or both.
Move fast is a requirement. Move fast while learning new gotchas doesn’t roll off the tongue quite as well. But this matters. Any technology you can ship with will always beat the best theoretical language on the market.
Some of my own stack decisions will be because I like the language and I’m good at it. Others will actually be because I’m trying to learn something, and I’ll call out those times too.
A final thought if you work somewhere that has a gross tech stack that you think inhibits hiring. They most likely told you their stack and you took the role anyway. Someone, somewhere will always tolerate a stack they don’t prefer to do interesting work that fulfills their career needs.
Don’t let perfect be the enemy of the good.
the easy decisions
Front end: Vue
I actually love Vue. The dust has settled on the 2 to 3 update debacle, and the team seems to have reached a good rhythm of improving both the core offering and the surrounding ecosystem (like pinia and vite).
I want to do an SPA and didn’t go with Nuxt because I will be dealing with quite a bit of dynamic data and exactly zero need for SSR. However, Nuxt is the shoe-in for any type of marketing site that i would make.
I considered other front end frameworks and libraries, but I ended up confirming my own earlier point. I am faster and happier with Vue than anything else.
Backend: Fastify
I am sticking with JavaScript (TypeScript really) on the backend because it remains my best language by far. I thought a lot about using this as one of my learning points, but I didn’t want the code and syntax to get in the way of getting to the next interesting decision. In a perfect world, I’d have probably tried golang or elixir, but especially since I’m not focused on concurrency or transactions or multiple simultaneous clients, I think I’m fine here.
As I’ll discuss more below, this is the first item I would consider throwing out in a real-world scenario if I needed to trim technical overhead.
Additional tooling: Supabase
I actually chose Supabase before Fastify. I have worked with Firebase with clients and I like supabase’s package with PostgreSQL over the former’s real-time database (RTDB).
There’s a couple of decision points worth breaking down here.
Unstructured data in RTDB isn’t my vibe much these days. Schemas and migrations will slow me down, but I chose a relational database and structured data because I wanted to be able to produce slightly slower in exchange for more confidence. Supabase also plays well with TypeScript.
Having auth be through a SaaS platform was actually something I thought absolutely zero about. I’ve homegrown an Argon2 auth system using Passport.js, and I’ve led a team that built a Python Auth microservice only six weeks past the due date. Supabase is easy, affordable, and handles the hard parts too easily.
I’m considering long-term growth too. Supabase offers serverless functions, works directly with the client, and even has automatic CRUD endpoints. I don’t need an API layer at all. Here, the tradeoff is that I’m setting myself up to divest from Supabase more easily in the future by investing time building middleware.
I also can’t help but feel better having secrets managed on the server side instead of client side. So Fastify gets a stay of execution for now, but it’s not going to be where the work gets prioritized to start.
the overhead of future proofing
Let’s talk about the harder decisions now. I had to deliberately work through a few decisions that came harder than the previous section.
First, while I am building something small, I have to be empathetic even to myself. Does automating a task now to avoid later repetition actually pay off? Are each of these decisions forward thinking or over engineering? Am i finding reasons to pick a technology because i want to learn it? I’ll try to clarify as I go where I landed for the next couple of additions.
Docker / Compose
One of the most consistently useful tools I’ve seen when done right is containerization, specifically Docker. However, I’ve actually never entirely understood how to containerize a full project because I’ve never been the one with the opportunity and need to do it. Or someone that had already done it provided the tools because “it’s easy to pick up.”
I have tried a few times, with different levels of success. I always find a complication like running dev builds with Hot Module Reload versus deployment builds with artifacts. When is it actually just best practice to exec
in and do a thing? What commands can I run and in what syntax within a dockerfile. Friction causing impediments that made me bounce off and go back to manual deployments.
So, I choose to take on this overhead. A single developer probably doesn’t need Docker. One or two machines configured similarly and running dev builds probably doesn’t need Docker. A single deployment artifact to a single cloud instance probably doesn’t need Docker.
One caveat though: Supabase SaaS does not play well with Docker. Since I’m not self-hosting from the ground-up, I won’t be tackling a full self-hosting setup. When trying to use supabase start
as part of a compose file, things get messy because Supabase spins up its own containers. Connecting to the platform is the right balance on this one.
But it’s still better to do it now than try and shove it in later when the quest for microservices has been successful.
the pivot
This wraps up the immediate stack decisions.
There is quiet joy in being able to take all of these items, create tasks, and start delivering actual code. I like to think that this heavy thought work alternated with action reflects the day-to-day of many engineers out there, even if it might feel a bit anticlimactic right now.
When I’m finished with the setup, I will have my own boilerplate that can be replicated and enhanced while not caring yet about hosting, deployment, or flavor of git repository. I’m going to commit to one more deliverable though, an Architecture Decision Record.
The stack has been decided. Next time, I’ll start talking about local development and some of those decisions. Routing style, state management, automatic linting and formatting, and more.