How to Choose Your Tech Stack When AI Can Code in Anything
The backend for Keonda is a Java application. I know how that sounds as a tech stack choice in 2026, and I know there are some strong arguments for Go, for Node, Python, etc. Here’s the thing though: AI doesn’t care which one I pick. It’ll write Go, Node, Python, or Rust equally well, with roughly the same effort either way. I made the call anyway, and I want to start here because it’s the decision I expect people to question first, and because the reasoning behind it is the same reasoning behind everything else in this article: not “what’s best in the abstract,” but “what I can honestly stand behind and review.”
Eleven years in Java means that when an AI hands me a chunk of generated code, I can tell - fairly quickly - when something’s off. Not always, but often enough. If I’d picked a “cleaner” or more exciting language I don’t know as deeply, I’d be reviewing code I understand less well, which is the opposite of what I want when my goal is to ship rather than learning.
That’s the lens for everything else here too.
(This is the first article in a series documenting how I’m building a real product with AI in the loop. The introduction to the series covers what it’s about and who it’s for.)
Why this matters more than it looks
Judgment is what keeps you in control when you are not the one writing every line. Your stack choice is the first decision you make in a product, and it’s the longest-lasting one. Everything that comes after gets constrained by what you picked now. Your architecture, your tooling, how you work with AI - all of it lives within those constraints.
Web App vs. Native App vs. Cross-Platform: What I Picked, and Why
Web First, With a Path to Desktop Later
I chose to make Keonda a web app initially. A web app reaches everyone without asking them to install anything - it would let me validate the idea fast.
And I still kept the path to “more native” open: Electron (or similar) can take a React app and ship it as a desktop app on macOS, Windows, and Linux with relatively little extra work. Trello and Slack, to name a few, do exactly this.
If Keonda were primarily a phone experience - something people reach for between tasks, like a habit tracker - I’d build native, separately, for iOS and Android. Apple’s tooling in particular (Swift, SwiftUI) gets you faster approvals and better AI assistance, and replicating feel across platforms with something like React Native is a fight I’d rather not have unless I need both app stores fast.
This product isn’t that. Builders spend most of their working day at a desk, not locked to any specific OS. A browser is where Keonda lives, for now.
The one gap web doesn’t close on its own is offline. I’m someone who does a fair amount of work in transit, with connectivity that comes and goes. That’s not universal for Keonda’s target users, but it’s a real enough scenario that I want the architecture to remain compatible with an offline-first approach later, even if I’m not building it now. I haven’t fully solved this. It’s more “don’t paint myself into a corner” than “here’s the offline strategy,” and I expect that to evolve.
React + TypeScript - and Why Strict Typing Matters More in the AI Era
React + TypeScript on the frontend. I didn’t overthink this one. It’s the path of least resistance. Both have the most training data for AI tooling, better resources, and stronger AI assistance than other UI languages and frameworks. I’m mostly a backend developer, so that mattered. I wanted something with good AI support and a clear path forward.
What I do think is worth saying is why TypeScript matters more, not less, right now. The argument used to be “typed languages catch bugs.” True, but there’s a second argument that’s become more important to me, especially in the AI era: when a meaningful portion of your code is AI-generated, your relationship to that code shifts. You’re reviewing more than you’re writing from scratch, and there’s always pressure to move faster than a careful read would allow. Strict typing is a compensating control for that. It won’t catch everything, but it catches enough, reliably, to be worth the friction.
In the backend I keep my tests in Groovy on purpose, since it’s dynamically typed. That flexibility is a feature there. I don’t want the same strictness getting in my way when I’m writing a quick test setup that I want when I’m reviewing application code. This isn’t about typing everything always. It’s about typing where the cost of being wrong is highest, and I’ve decided that’s the application code, not the tests.
The Backend Language I Can Actually Review
Java (Micronaut) on the backend. I covered this at the top of the piece. I won’t rehash it here; the short version is that depth in a language is the one thing I can’t shortcut by picking a “cleaner” one I don’t know as well, and review quality is bounded by that depth. Java is clearly still relevant in 2026. The real question is whether I can review it well enough to ship without a team backing me up. Today, the language I can audit confidently beats the fashionable choice.
It also helps that comfort and the right tool aren’t pulling in opposite directions. Java has one of the largest, most mature ecosystems in the industry. Two decades of libraries, tooling, and Stack Overflow threads for nearly every problem you’ll hit. That’s not a “comfortable but fading” position. It’s still one of the most widely used languages in enterprise software, with no real signs of that changing in the next several years. So this isn’t me trading correctness for comfort; the language I know best happens to also be one that’s well-supported now and will likely stay that way, AI assistance included.
Why PostgreSQL Over Firebase and Supabase for a Solo Dev
PostgreSQL, chosen quickly, but not carelessly, because this is one of the harder layers to walk back later. Relational databases have been the default for so long for good reason: there’s an enormous body of accumulated knowledge about how to model, scale, and debug them, they’re battle-tested in production at every scale that matters to me, and the failure modes are well understood. That predictability is worth a lot when you’re the only one reviewing the architecture.
I know there are good alternatives. Supabase and similar providers offer generous free tiers and a much simpler mental model if your access patterns fit them. For some products that’s the right call. But for mine, the decision was straightforward: nothing in my core use cases pushes toward the tradeoffs those tools are optimised for, and a relational store maps cleanly onto how I think about the data.
That said, “straightforward” doesn’t mean “skippable.” Storage is one of the most expensive things to change once you’ve built on it, so even when the answer seems obvious, it’s worth a real conversation with someone who knows the space better, or with an AI, about your actual access patterns before you commit. In my case that conversation confirmed what I already suspected rather than changing my mind, but the point is I had it. The “managed vs. self-hosted” question I’m leaving for later, since either is defensible at this stage and I didn’t want it to delay shipping.
Pick the stack you can ship with and audit alone, not the one that looks best from the outside.
The Prompt I Use to Make Stack Decisions
This is the prompt I’d hand an AI to work through these same decisions on a new project. Basically a prompt for choosing your tech stack from scratch, structured as a conversation rather than a one-shot answer. It’s deliberately structured as a conversation, not a questionnaire. The value is in the back-and-forth, not the output.
Take it as it is and adjust to your needs.
I am starting a new software product and need to make my core stack decisions before writing any code. Help me work through them in order. Before starting the layers, ask me to briefly describe the product itself — what it does, who it's for, and what problem it solves. Use this as context throughout the rest of the conversation: if something I describe here already answers or shapes a later question, don't ask it again from scratch — instead, reference it back to me and confirm or refine rather than starting cold. Do not give me a final answer until we have gone through each layer. Ask me one question at a time and wait for my response before continuing. As we go, gauge how I'm engaging with each question. If a concept seems to be landing as unfamiliar or my answer suggests I'm unsure what's actually being asked, don't push forward with jargon — quietly adjust your language, fold in a brief plain-language explanation as part of the question itself, or rephrase around what the underlying tradeoff actually means in practice. The goal is for the conversation to stay useful and natural regardless of how deep my technical background goes. Start with platform and distribution: - Who are the different types of users in this product, and for each, where and how do they primarily interact with it — phone, desktop browser, app, in person, etc.? (If different user types have very different contexts — e.g. one side runs a business through it, another side just uses it occasionally — tell me about each separately.) - How important is offline support to the core use case, for any of these user types? - Do I need to be on multiple platforms at launch, or can I validate on one first? - What is the cost of being absent from a platform in the first six months — for each user type, if it differs? Then move to language and framework: - What languages and frameworks am I strongest in — not what I find interesting, but what I can move fast in and review confidently? - Who is my target audience, and what technology do they already live in? - I'll be relying heavily on AI tooling for code generation, so factor in that strict typing becomes more valuable as a compensating control, and that mature, widely-used languages have better AI support than newer ones. - What complexity can I cut? Every framework and abstraction I add is something I'll need to understand, configure, and debug — keep the overall footprint as low as I can get away with. Then persistence: - What are my core data access patterns — what do I write, what do I read, and how often? - Where do I need strong consistency, and where can I tolerate flexibility? - Can I manage infrastructure myself, or do I need a managed service? Once I've answered all of these, do two things, as separate, clearly labeled steps: 1. Summary — Go through each layer and state the choice along with the reasoning behind it — what about my answers led to this option over the alternatives. Each layer should land on a single, decisive choice — not a list of options like "X or Y." If two options are genuinely close, pick the one that better fits everything I've told you and explain why. 2. Challenge — Then, separately, go back through the choices and challenge any that seem to be based on novelty, trend, or familiarity with the wrong thing rather than fit for my actual situation. Be direct about it — if my own stated experience or constraints cut against a choice we landed on, say so explicitly, even if it complicates the summary above.