Letâs imagine:
Itâs 2025. Youâre a software developer. You joined this industry a little more than 5 years ago and you enjoy what you do.
Youâre building a web-based application with a small team. You crank out features, one after another. Some of them feel kinda useless, but youâre learning a lot and having fun with your team, so youâre not complaining.
The team youâre working on is frontend-heavy. Your frontend is written in a JavaScript framework (itâs React, isnât it?). A while ago someone on your team introduced you to the magic of React Server Components and how they solve a whole slew of problems with traditional SPAs. Your team decided to adopt server components (youâre on Next.js now, arenât you?). You style your frontend using Tailwind, maybe you use shadcn/ui for some of their sweet UI components. All of this is pretty standard stuff, and itâs what the folks on Reddit and Hacker News keep raving about.
Hold on.
Maybe I got that wrong.
Youâre actually not one of those frontend weirdos. Youâre a backend engineer. Youâre not one to push pixels. You push data into databases and pull it back out using a plethora of CRUD endpoints. You sing the gospel of Go. You build a nice GraphQL API so those frontend teams donât bother you with new requirements to tailor your API to their needs all the damn time. On the rare chance that youâve actually got to serve a frontend from your backend application you decide to serve HTML with HTMX sprinkled on top for extra interactivity. Thereâs no way youâd touch any of that lousy JavaScript yourself.
You deploy and run your application on Vercel, or you host it with the quirky folks at Fly.io. As your organization grows, you start packing everything into Containers and start deploying to a managed Kubernetes cluster hosted on AWS. Youâve got a bunch of tireless tired SREs at your company who manage all that âk8sâ madness for you. On top of that they provide you with nice dashboards showing metrics, logs, and all sorts of events â Observability as they call it. You wonder why theyâre always so tired and on the edge, but youâll leave solving that mystery for another day.
Whatâs your point, grumpy old fart?
Itâs unlikely that I know you or the company you work for. Still thereâs a good chance that the strawman I built above isnât too far off from your day-to-day work. I probably struck some chords and revealed patterns, tools, and frameworks Iâd find if I joined your company.
Despite my grumpy undertones, Iâm not implying that any of the technologies and patterns I listed above are bad. They arenât. Thereâs hardly anything thatâs clearly good or bad in software engineering1. These tools and technologies are popular for a reason. They allow engineering teams to be effective and keep on building, shipping and running software reliably. They give you practical abstractions so that you donât need to worry about whatâs beneath. You can focus on building the next big thing instead of getting lost in the weeds of frontends, backends, servers, operations, and keeping things up and running.
When I built and published my first web application a little more than 20 years ago, none of the tools and services I listed above existed. Lately Iâve been thinking about how different it was to build, ship and maintain software at different points in my career as a software developer and how grateful I am for having experienced different stages of our industry. With a plethora of modern software engineering power tools at our finger tips itâs easy to lose sight of the foundations. Writing software like itâs 1999 is still doable (and more fun than ever), but largely underappreciated as software developers quickly fill their developer toolbox with very powerful but highly complex tools as thatâs what everyone tells you to do.
This might all sound a lot like a âback in my daysâ anecdote. Some of that âreal programmers use blood, sweat, tears and punch cardsâ nonsense people have been saying at pretty much any time in software engineering history. A nostalgia-rich plea that you return to building production software without any of the tools weâve got today. Donât worry, thatâs not what Iâm trying to say here. You know how to use modern tooling so itâs smart to use it.
However, I do think that thereâs a lot you can learn from purposefully building software the way people did decades ago and I want to encourage you to give it a try.
Take the under-engineering challenge
Iâd like to challenge you to deliberately under-engineer a small web application. Get it all the way out there to production, with a public URL people can visit. It doesnât need to be anything sophisticated. Pick an application that you can build relatively quickly, this whole adventure should take a few hours, tops.
If you need inspiration for an application you could build, how aboutâŠ
If you donât like the ideas above, pick one of your own. Donât overthink it, just have some fun and get started.
The rules of the under-engineering challenge are simple:
- you build a web-app
- you keep the amount of frameworks, libraries, tools and services to an absolute minimum
- you deploy it to a web server thatâs not your local machine
- you publish a URL that people can access on the open web
Under-engineer like you mean it
When I say âunder-engineeringâ I mean that you should purposefully avoid anything you can cut out from your tech stack. Aim for radically simple. If you think you canât go simpler, try harder. If you donât feel bad for the scrappy nature of your code and infrastructure, youâre might not be trying hard enough or youâre more comfortable with scrappiness than most.
Write static HTML by hand. No templating engines. No static site generators. No frameworks.
If you want to add JavaScript, use vanilla JavaScript. No build tools. No bundlers. No tree-shaking and minification. No TypeScript. Inlining JavaScript in <script>
blocks in your HTML is totally cool. No third-party dependencies like HTMX or Alpine.js or jQuery - the DOM API is plenty.
Write CSS like your great-grandparents did. No Tailwind. No Bootstrap. No PostCSS. Just honest to god <style>
tags (or damn inline style
attributes for all I care!).
If you write a server application, try to use good old server-side rendered HTML instead of relying on single page applications or server-side components that you hydrate on the client. Try to use the smallest amount of frameworks and libraries possible. No third-party dependencies.
Grab your own server. Maybe youâve got a spare laptop or Raspberry Pi sitting around that you could expose to the internet (for example via dynamic DNS, look at Cloudflare or ngrok). Or you rent a cheap VPS at Hetzner, DigitalOcean or grab a free AWS EC2 instance.
Donât bother containerizing your application. Donât mess with terraform
or kubernetes
or docker
. Donât rely on PaaS providers to do the heavy lifting for you. ssh
into your server, provision it manually by installing packages via apt
. Write config files by hand. Donât worry about Infrastructure as code. Donât worry about Continuous Deployment. Copy your application over via scp
, rsync
or sftp
and start it on your server. Hook it up to a DNS record manually and youâre good to go.
To understand how your application is doing in production, you could ssh
into your production box and tail -f
some log files. If your app keeps crashing, bring it back up - maybe you start writing a cron
job or a systemd
service to keep it alive.
Out with the new, in with the old. You get the idea.
I went through this exercise myself as I wrote this blog post. I built a simple âair hornâ web app.
Itâs silly, itâs simple, and it was super fun to build. All the code lives in a simple
index.html
file Irsync
to a cheap VPS that I installedCaddy
on. You can check out the code in this GitHub repo.
Forget conventional wisdom and see what you learn
Most of us havenât been programming long enough to remember a time when the comfort of modern tooling didnât exist. All we know is the cushy and yet mind-blowingly complex world of modern software development. The layers upon layers of abstractions, tools, and âbest practicesâ they hurl at us on all the usual social media channels.
When weâve been around for a while, it becomes easy to get stuck in our own ways. As we grow as professionals we get comfortable with more and more tools. We create our personal developer toolbox and fill it with tools we know and love. Most often the tools that find their way into our toolbox are not the simplest tools to get the job done but the ones we picked up because they were popular. Once we grok JavaScript frameworks and elaborate container orchestration itâs tempting to use these complex tools even for simple problems out of habit and comfort.
Look, Iâm not some minimalist zealot telling you that youâll only achieve true programming zen if you start saying ânoâ to all the comforts the modern programming world is giving you. Under-engineering is merely an exercise to challenge your beliefs in a healthy way. Disregard everything you believe to be best practice and common sense, get uncomfortable, and pay attention to what you learn. When you go through this exercise, I promise that you will learn and experience a few insightful things.
- You get a chance to understand the primitives, the basic building blocks that hide beneath all the tools you use daily.
- You start to appreciate the convenience that modern tooling truly offers.
- Equally, youâll discover that basic building blocks are often good enough and get you further than you expected.
- You might learn that there are hardly any best practices in software development, only trade-offs that depend on circumstances.
- You get an opportunity to replace complicated parts in your developer toolbox with simpler alternatives.
- And of course you get the bragging rights of having shipped something in a completely unconventional way.
Use modern tools, but understand their cost
Under-engineering is a fun exercise. But itâs still just that: an exercise. Please donât go to work next week and tear down your production Kubernetes cluster because some random guy on the internet said itâs fun to deploy software to your own VPC via ssh
. And if you do, please donât blame me.
Modern software development ecosystems allow us to build things we couldnât build decades ago in a fraction of the time. At the same time we often fall for snake-oil, over-engineering, and chase the newest shiny tools for kicks. Being able to understand when exactly modern tooling is pulling its own weight is an important skill for a well-rounded software developer.
While I think itâs healthy to reduce the complexity of your application stack I donât suggest you reject modern tooling altogether. Software is a fundamental part of modern life. Our users expect our systems to be responsive, reliable, accessible, and pleasant to use. Software development is more than hacking on a system by yourself - itâs a socio-technical problem that requires humans to collaborate closely and find creative solutions to meet ever-changing demands. Pick tools that make you and your team effective and donât reject modern tools to tickle a weird purist sense superiority. But please be mindful of the additional complexity you introduce.
I encourage you to go radically simple and under-engineer on purpose every now and then. It can help you become a better software engineer, help you re-evaluate the tools youâre relying on, and if nothing else itâs a fun experience.
If you go through this exercise and end up publishing something, Iâd love to see it. Hit me up on one of the social channels you see linked at the bottom of this page and show me what you came up with!
Footnotes
-
The biggest gripe I have with modern software engineering organizations is the seemingly default split between frontend, backend, and operations teams, but thatâs something for another day. â©