Today I want to give an opinion piece on JavaScript on the server.

This is going to be a bit of a hot button, click-baity topic, and I’m sure some people won’t like my take, but I’ve wanted to give my 2 cents on this for a while. So here goes.

I don’t believe that JS belongs on the server.

Certain newer frameworks are trying to transition to full-stack JavaScript where on the frontend you’re utilizing something like React, Angular, Svelte or something along those lines. Then on the backend you have a NodeJS application (Or Bun if you’re cool). Vercel’s NextJS takes things a step further and provides you with a framework for all of this, and includes new JavaScript server side rendering improvements and server actions.


So how did we get here?

You could easily argue that, because JavaScript is so easy to learn, and because it’s the only language widely supported by web browsers on the client side, that we were always going to be headed this direction. Until WebAssembly really takes off, JavaScript is really all there is for frontend web developers other than PHP. Even if WASM does gain widespread adoption, the browsers themselves will need to support it, and most likely people won’t write WASM by hand, but instead write in something else, that then compiles to WASM.

But why is there so much focus in the JS community on the server side suddenly? For this I blame, a desire for integrations to address poor client side performance and poor API design.

On Poor Rendering Performance

Certainly if you are designing a web application with appropriate pagination, data structures, and state management, there’s no reason we really need to move rendering and actions that would typically be done on the frontend, back to the server again. Is there?

After all, isn’t the need for client side rendering exactly where tools like React and Angular came from? Just a few years back we saw a big move towards client side rendering and code execution. Servers in the cloud were expensive, and they still are, and at scale we were running into problems with trying to render webpages and simultaneously perform all of the other necessary compute operations on our servers. To address this, we moved rendering to the client, distributed the workload, and for a while things were great.

Since then the average mobile device has only gotten more powerful and ubiquitous, while the server side of things has moved towards microservices and lambda functions, where the servers really don’t have a whole lot of resources anymore. My hot take here, is, unless you’re coding Fortnite in the browser, there’s really no reason the average smartphone with 8 gigs of RAM and a quad core processor shouldn’t be able to handle rendering your JavaScript web app.

If your web app can’t be rendered by the client, and your application’s focus is not by nature graphics intensive, chances are something is wrong with your approach, and increasing horizontal or vertical scaling in the cloud to deal with that issue, is not really solving the problem, but just treating the symptom at the cost of more company money. The average cell phone, on paper, has significantly more resources than the average microservice container. Why oversize your cloud resources to handle a job that can be distributed with relative ease?

On Convenience Over Safety

There is also the question of stability. JS doesn’t have built-in static type checking. TypeScript and JSDocs are great, but there’s no guarantee every library you utilize uses TypeScript, because it can interop with plain ole JavaScript. Even if all the code you write is type safe, there’s still the chance your dependencies are not. I don’t like this for the server side because to me it feels unsafe. Even the most carefully laid plans can go awry if just one input changes. I also wish that JS had more features for null safety and better parallel processing support. Runtimes like Node, Deno, and Bun offer some parallelism via worker processes, which is useful, but rather heavy.

All that being said, I’m guilty myself of trading safety for ease of use. This website is powered by WordPress, which is powered by PHP, which is neither type safe, nor null safe. You are probably wondering, then why would you use it? Convenience. I don’t have to write code to maintain a decent website. I don’t have to worry much about dependency updates, the core plugins of my site and WordPress itself are configured to automatically upgrade. To keep PHP up to date, I use an Ansible playbook that makes sure my Linux boxes get routine patches. For a small business like mine, saving time and effort is incredibly important, and I think this is why JavaScript is quickly making its way to the server.

Don’t get me wrong, I think the flexibility of JavaScript is great, and I think that with proper tooling, perhaps an opinionated framework like Next.JS, you could have something relatively safe and stable. While frameworks do mask many crucial elements of application development, they also tend to enforce best practices and reduce overall risk, while simultaneously providing that relief from boilerplate that we all so desperately seek.

On the Usage of Libraries in Frontend JS

Another reason I think JS is making it’s move to the server is overuse of libraries on the client. NPM itself has a whole host of supply chain security issues, but putting that aside, two of the most important things for a responsive web app are bundle and payload sizes, and I see a lot of commonly used libraries that balloon the size of a JS SPA, for no good reason.

For example, let’s talk about form libraries in React.

Not to name names, but two of the biggest form libraries on NPM (for React specifically) are each absolutely massive in size. One is over 800KB and one is 500KB. But when a user is interacting with a form in our application, how many form fields are they interacting with on average at one time? People are not robots. They’re editing one form field at a time, or maybe their browser auto-fills everything on a single page. Even still, how many form fields does the average page really contain? So why do we reach for 800KB of dependencies, to handle something as simple as forms? Why do we need thousands of lines of boilerplate, and supposed performance improvements, just to improve interaction with a component that can be handled directly in HTML5?

Perhaps the problem isn’t really rendering on the client, but bundle sizes and extraneous operations due to overuse of libraries to perform what should be fairly simple tasks. I feel this way, because just like with my use of PHP on the server, I’m guilty of it myself. With forms in React I like to use the yup validation library. I really like yup, I like schema based validation, and I like not having to write ugly regexes for things like emails, by myself. At time of writing, yup is 248kb alone. Every time I add it to a project, I add all of that boilerplate to my web apps bundle size, without understanding really what’s going on under the hood when I use it. That’s a lot of tradeoff, just so I don’t have to write a couple regexes.

Yes… I know once minified these libraries aren’t nearly as large as they are on disk, but nonetheless, are they really improving upon the default experience?

Another example I could give comes from my day job, where I’ve seen project managers request charts containing thousands of data points. No matter where this rendering happens, it will be slow. The server might be faster in general, but because the scale of the data is so large, without significant parallel processing, there is no way to render it efficiently. Ergo, to make your server more quickly render what needs to be rendered you’ll need to throw more resources at your server, or spin up more servers, then you have to consider that this server needs to render for not just one client, but potentially hundreds to thousands at a time, and suddenly your microservice is not so micro anymore. To make matters worse, if you are using JavaScript on your server, as mentioned before, you lose out on some of those parallel processing abilities due to the nature of the language.

Conclusion

So there you have it, maybe I’m just an old man yelling at JavaScript. I could go on for days about it, but I guess my thoughts are, is it really worth looking at every problem as a nail to be hit by the JavaScript hammer, or should we start considering that maybe it’s time for a change? JavaScript has solved so many problems and the language is simple to learn and easy to work with. Without a doubt, it’s changed the way we think about programming, and it is an amazing tool for prototyping and creating quick proof of concept projects, but I can’t help but wonder if it is starting to have a negative impact on the way we design our backend systems.

**BONUS**

After posting this article, the question I received more than any other was, what’s your ideal server stack?

To be honest, the languages I know best don’t really fall into my category of ideal. I use Java primarily which although it does a great job for my purposes, can be verbose and fickle. On the opposite end of the spectrum, Go has always felt incomplete. I can easily do very simple tasks in Go, in just a few short lines of code, but trying to do something complex in Go feels like reinventing the wheel, every time. That strikes me as dangerous. Of all the languages I have used over the years, one stands out to me as a great candidate for server applications that need to be spun up quickly, safely, and performantly, and that’s Kotlin. I am still new to Kotlin and the syntax feels a little odd to me, but it checks all of my boxes. Kotlin has:

  • Type safety (by default)
  • Null safety (by default)
  • Sealed classes
  • Mature and efficient concurrency/parallelism
  • Interoperability w/ Java and all of its matured libraries
  • Good GC
  • The ability to be compiled to native code

If I had to pick a framework or library for a new server-side green-grass project today, Kotlin would probably be my language of choice, and I’d go from there.

Thanks!

Categories: Thought Piece

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.