Developing a Modern Full-Stack Application: Choosing the Right Tech Stack
isultan.bearblog.dev
isultan.bearblog.dev
These technologies are great for prototyping and building a v1 release to see if what you're trying to achieve is actually possible, but you will regret it later on.
The reason I know this, I work at a startup where we literally had the same backend stack and its been nothing but preformance issue after preformance issue. And it all needs to be replaced. We would have been better off building everything with go/rust in the first place. Or even java.
1. I agree, Prisma is not a great ORM. Drizzle is a better choice. It's close to the metal and when the abstraction inevitably leaks, it leaks towards the user using raw SQL
2. Modern JS is extremely performant. Look at any of the benchmarks for the new JS runtimes that have come out in the past 6 months (e.g. Bun / WinterJS / etc...). It approaches Go / Java in terms of performance.
3. Even the traditional NodeJS runtime has been optimized out the ass by Google. For example: JSON parsing has highly performant SIMD instructions under the hood. When a trillion dollar company puts billions of dollars behind a technology it will get fast.
4. There is no possible way that building your CRUD backend in Golang / Rust is a "faster" solution than just using React Server Components.
5. The vast majority of startups are IO-bound, not CPU-bound - so "fast" languages like Go / Rust won't be as relevant.
The benefits of Go (for most companies) only apply once your company hits an inflection point and starts to scale and starts to see the throughput that can really take advantage of a lower-level language.
If you're building a high-throughput infra company or something, then the things I've mentioned are less relevant.
https://www.techempower.com/benchmarks/#hw=ph&test=composite...
I like JS but lets not blow smoke up anyones ass here. Your not picking node or its faster safer cousin bun for server speed. You're picking it because you can keep your engineering overhead to a smaller number. Your picking it because you need to generate pages out of your SPA like app.
> 4. There is no possible way that building your CRUD backend in Golang / Rust is a "faster" solution than just using React Server Components.
Spend a month with SQLC and writing blood and guts API's in go. Your gonna realize that the slight slow down in finishing the feature code is more than made up for in finishing testing, ease of deployment and a million other things that you skip by NOT using a JS backend.
>> The benefits of Go (for most companies) only apply once your company hits an inflection point and starts to scale and starts to see the throughput that can really take advantage of a lower-level language.
This is some holdover thinking from PHP vs JAVA days that isnt true when comparing node/ruby to go/rust ... Im going to leave python out because you may have OTHER reasons (existing bindings to C ...) to head in that direction.
>> If you're building a high-throughput infra company or something, then the things I've mentioned are less relevant.
Maybe this is true. But I worry about the long term stability and paintability of anything written in JS. So much flavor of the month and then poof, off to the next thing, it IS a concern.
I’d love to see some of the codebases where people complain about performance so I could profile them myself. Would put money on being able to improve the situation by orders of magnitude without switching stack.
We use Python on the backend of our web app for realtime image recognition and it’s fine because we’ve been thoughtful about data structures and algorithms.
I have no qualms with python. But Python is the high fructose corn syrup of programing languages... It's in everything.
It's dead easy to write out c bindings so from ML to Math to big data tasks it forms the glue to a lot of things.
My question is: is your real time image processing IN python or in C that python calls out to?
My point is that for most usecases you can go a lot further by looking at the code that’s running rather than the language that’s running it.
One big issue i have though: the lack of easy multithreading reduce your possibilities, or at least limit your creativity: you will often choose to use async/await (and sometimes use signals) rather than use producer-consumer designs, which are often the optimal solutions.
And nodejs only preforms well in hello world benchmarks, real world applications are nothing like that. Once you start having to manipulate large arrays or do any large amount of math nodejs preformance goes into the dumpster.
We’re talking about web applications, no? You probably shouldn’t be manipulating large arrays or doing large amounts of math directly in your web application server. That should be isolated in some type of service or worker, which could be written in another language. Or maybe there’s a NumPy-like package for Node.js, I haven’t looked.
The question then is where do you draw that line of using another language? It probably depends on your application, but I think Node.js is perfectly suitable for typical web applications backends.
There are other kinds of solutions for this problem like breaking up the data into chunks and only returning the necessary data. Maybe it's a DB optimization where you can add indexes. Or caching the result of your ORM query.
I've never seen any web app written with any tech that was snappy while making requests for large amounts of data and waiting for it to come back in one big honkin array.
> Web apps deal with fairly big data structures, if nothing due to orms.
Perhaps due to using ORMs unnecessarily and/or inefficiently and failing to drop down to SQL when needed.
I recently had to process and aggregate metrics for 5 million rows of user data (a few GBs) on my MacBook with Node.js. By streaming / iterating over the items without loading them all at once it chewed through them all in a few seconds. ¯\_(ツ)_/¯ And it's single-threaded (except for I/O offloaded to threads -- I'm talking about the calculations).
No, it's not TB, it's a few GB for small projects, and tens of GB for large projects. Rarely does a project ever get over 30-40GB.
> Obsession with async...
If you're going to want any amount of decent performance, you're going to have to deal with async. That's just the nature of IO bound applications.
VSCode disagrees with you. Its codebase is fascinating btw.
I'm saying this because i myself have, and lots i know, have launched production sites with 100s of thousands of users on ready-made stacks like, Laravel, Rails, Flask (Php, Ruby, Python). But it's my impression that these fall short on millions of users and enormous concurrent traffic, but then you're already at huge evaluation, years into your project, or have 200+ positions, ie. you've already refactored your project multiple times.
Simple crud apps can get by fine with those technologies, but in the future I'd still never use it because you're leaving huge amounts of performance gains on the table for virtually no benefit. I don't buy the argument that javascript is just easier to develop for because it's simply not. The js ecosystem is a disaster.
Getting stuff that people value enough to pay for, to make money, is the hard part.
Any friction you put into the value creation process because "optimizing for future problems" is just doing it wrong.
Again sounds like you are running some complex math heavy operation like say a weather service, national taxi service with lots of pathfinding, a global gaming platform etc?
Curious to know what exactly you are talking about here? Because normally you just outsource the "heavy stuff" to some remote API, service, etc. you can write in a hyper efficient language anyway.
If you need someone with experience working with Rust and Svelte, I’m looking for work and value making quality decisions like you did. Now you can point to at least one dev who has the skills your team lead was concerned about finding.
Depending on the company you work for, this was probably a good call on their part.
Application servers should scale out, sure you might need a few more for Node than Rust, but is that really where the issue lies?
You wouldn't implement Postgres, Ceph, QEMU or the kernel in Node... But the CRUD part of most applications will be fine in "any slow language"
Like LinkedIn! Except that to make it performant they had to horizontally scale and then restart the server every N hours when they ran out of memory…
Can you elaborate on this?
type Blog = { name: string }
everything works fine until you decide to refactor "name" to title. you update the backend and typescript code and deploy the change
type Blog = { title: string }
user john never closes his browser and leaves your website open. he clicks on a new blog post. his client typescript expects a name, the server gives a title, and crash, "Cannot read properties of undefined".
i still choose nodejs backends, but you obviously have to keep in mind it's not statically typed. same with "any", my coworker could take my blog and modify it. it's preventable, but with large teams and codebases still prone to error.
(blog as any).title = { summary: 'blah', full: 'blah blah blah' }
And in your example of loading data from a server, if you have appropriate lint rules set up your editor and linter will warn you when you pass “any” typed data (such as that returned by fetch’s response.json()) to something expecting another type. You can use something like zod or yup to validate the data is the type you expect before passing it the rest of your typed code, thereby containing possible type errors to a known location where you can gracefully handle it.
This is a problem with any language that accepts typed data from an external system, certainly not unique to TypeScript.
it's impossible to debate more without going into details on your performance issues. typical backend architecture for any platform these days is the scalable container model, if you wanted scale-to-zero i wouldn't necessarily use node because of cold starts.
prisma can be great, can be slower than writing your own query, but that's the whole point of it. most of the time, it works and you can forget about queries and typings. when it doesn't, you can just eject to raw sql anytime you want.
i'm not a total nodejs fanboy, and have used more jvm (java/kotlin/scala) in my life, but if i was building a web-app today i'd absolutely consider node for all the reasons the author listed
‘nodejs is too slow’ is high school level backward rationalization.
‘nodejs is too slow for my use case’ is getting there.
Most stuff we engineer maxes out the database way way waaaay before it saturates its CPU capacity regardless of the language used. You’d better know very well that you’ll be vulnerable to this as a business, otherwise your inefficient competitors will take your money faster than you can build value.
[0] - https://www.techempower.com/benchmarks/#hw=ph&test=composite...
Deno would have probably scored well too (it uses Rust's Hyper crate under the hood), but they're only running a single instance of the server despite Deno supporting the Linux SO_REUSEPORT socket option, which is important because the test is run on three servers with Intel Xeon Gold 5120 Processors that have 14 cores and 28 hyperthreads [0].
[0] https://github.com/TechEmpower/FrameworkBenchmarks/tree/9f0c...
> the whole premise of node was async io, so if you're doing a bunch of blocking stuff then yeah, you're going to have issues.
That right there is the rationale behind the "Choose Boring Technology" movement: https://boringtechnology.club/
I don't know whether to feel old or cry on my dedicated servers.
The same sort of arguments could be said for "choose what you know", "choose what you understand", "choose what you can hire for", "choose what you can afford".. the list goes on.
Like most things in life there are no cheat codes.
What I overlooked is that while the platform might be popular and mostly trustworthy (such as Vercel), there are specific solutions like the Vercel Cron Job are still relatively new (released Feb 2023) and still not ready for serious use.
Managed hosting can give you a head start, but also increased costs. There are quite a few horror stories when it comes to billing.
It's a meme that running your own DB means that you'll get owned within seconds of going live, or lose all your data in the first day because fires in your data center are a daily occurence :-/.
It's the modern equivalent of "MongoDB is Web Scale".
> Managed hosting can give you a head start, but also increased costs.
Yeah ... buuuuuuut ... it's a very tiny head start.
Sure, you may save a few hours by using a hosted postgres service, but you save that only once over the entire lifetime of the database server!
IOW, if you set up a PostgreSQL server on a couple of cheap VPS instances, you can continue using that server for each new product until you hit performance/security problems.
I've got exactly one PostgreSQL server set up, and all my little experiments happen on that one server, with me creating new databases as and when needed.
https://learn.shortruby.com/blog/tech-stack
It is nothing fancy and I say it is biased because I started with what I know. But I tried to get into details about each choice.
Personally I think ORMs are mostly bad and Prisma is more of the same. Zapatos at least gives you typed SQL results without a lot of overhead.
We migrated our main application to Remix and couldn't be happier. The app router stuff is such a mess.
As for the ORM / query building, did you also consider Drizzle and Kysely against Zapatos?
I haven't played with either Drizzle or Kysely, looking at the docs Drizzle in particular looks slimmer and more sql-like which is a plus, though in general I think having an environment like dbeaver or data grip that gives you completions for your queries and developing them there then copy/pasting the code and getting type safety on raw queries with zapatos is still superior for power users who know sql.
From all of this, the worst decision is to use Next. Vendor lock in, developed by one of the most shady companies right now, and full of problems.
My tech stack should have the potential to scale nicely, in case it ever needs it.
So I'm not looking for much, just Easy - Powerful - Future Proof
Sounds like the OP's stack is not it.
Golang + Echo + Templ + HTMX + Alpine.js + Supabase
I should add, I'm someone who heavily dislikes JSX and prefers SFCs.
What has your experience been like with them?
This is why people should use established backend frameworks such as Laravel/Django/Rails if they really want to focus on their product and not on reinventing the square wheel.
Some of the paper cuts described include:
- Inexplicable interaction between the data layer and a hosting service's environment variable
- Migrations that don't work reliably
- Cron jobs that don't work reliably
- Inexplicable caching behavior for serverless functions, requiring an obtuse workaround
- An auth library that makes it hard to... get the ID of the logged-in user, apparently
I dunno, I suppose I'm happy to stick with boring old tech.
Most of my experience has been with a more "boring" stack, so I was interested in trying some of the more "hyped" technology choices, to see if I was missing out on anything.
I’m also in a phase where I get to start a lot of new low-stakes web projects, so I’ve made a point of adopting at least one new-to-me tool for each. I love typescript, and am generally happy with modern front-end tooling, but the node back-end ecosystem drives me bonkers — too many paper-cuts from too many tools along the lines you’ve documented here — and has me regularly retreating back to Python or Ruby land.
https://github.com/pocketbase/pocketbase/discussions/395
Never happened to me. SQLite is faster than most people think. When you hit the limits of sqlite, chances are high that you can afford to use one of the scaling solutions mentioned above. Here are some benchmarks:
Could you recommend a stack please? * Testing - test runner, mocking * Database - Typesafe queries, ideally as little abstraction as possible, migrations * Web app framework * Dependency injections (or do you prefer doing it manually over using a library)
Only backend developers like it. As soon as you have to do anything minimally complex it's just terrible.
What I don't get is people just blindly "following the hype" (as with anything else) and assuming it's a replacement for client side frameworks (react, vue, etc) for anything you might need out there. You already said this several times in some Podcasts I listen to, so I don't blame you. But people are just "hey bro just use HTMX and Go and done". And that might be fine if you're a backend developer that doesn't like JavaScript and have a very simple use case and probably don't have high UX standards or picky designers chasing you with every animation, loading state or performance issue.
There are many projects out there with pretty complex UX logic where this will just not work, and if it works the end result is going to be much harder to maintain. Let's not take into account finding people willing to work with these tools (already happened to us where we had to move a Rails/Hotwire project to Rails/Inertia) because no one could work on the frontend, initially created by a backend dev and that became such a mess not even him could maintain it anymore).
But again, it is not the technology to blame (it's great!), it is the usage of it. And people can misuse anything, as they misuse react/vue/etc when they're building just a landing page.
It would be refreshing to me for someone to suggest we at least evaluate HTMX on a new project before going straight to the Next.js or Remix behemoths where we'll have 45min builds pulling in thousands of NPM dependencies over half a million files in node_modules.
Edit: I was also thinking it would be even more refreshing if people focused on web standards and asked whether or not they actually need a trendy framework.
You sound unhappy as well, and you also sound like you're doing something terribly wrong here.
My experience with people following the HTMX hype is backend developers annoyed by JavaScript not being perfect and node_modules size.
node_modules is never that big, and even if it is it's not a problem as big as maintaining complicated (i.e. not trivial) projects with these tools.
At the end of the day it depends mostly on your skills and the requirements for the project you're working on. As I said if you have some UX experience and you care about loading states, page transitions, animations and in general the little details then something built with these tools will be unmaintainable by anyone other than the person that implemented it in the first place.
node_modules too big? so what? that's not all of it shipped to the browser and I have plenty of space on my computer. A mess of html attributes, logic split between jquery, backend, html attributes, etc? Now that's a bigger problem.
And why I said "hype"? Well, because people suddenly see it as the greatest thing ever because some youtubers/podcasters made it popular, when there've been similar solutions for ages and nobody cared (i.e. Unpoly). But if the right youtuber talks about it, then boom! Everyone is saying everything should be done this way from now on.
As I said in another comment, the idea is great and I like it. But as the creator of the library said MANY times: It's not a replacement for component based frameworks. It's a great tool for the simpler use cases where react/vue/etc would be overkill.
My rant is about people acting as this is the future for everything, which again, the author of the library itself says it's not, and he's a smart guy.
I mean, yeah, you have to do those sometimes, but they should not be blocking midday deploys.
There are way more use cases out there than a simple web or mobile app.
In fact, there are already many intentional limitations that makes Next a very bad choice if you're self hosting.
import { compare } from "bcrypt";
If I wanted harden this setup, my next consideration would be either having a separate microservice that's purely responsible for auth, or using 3rd party provider like Cognito.