Skip to content

The AHA Stack

I just realized I haven’t written about the AHA Stack on the blog. I came up with that concept 1 year ago, in the fall of 2023, while developing the idea of building an app using Astro, Htmx and Alpine.js (AHA).

The general idea is this:

The AHA Stack is one of the many ways you can build a Web Application.

The stack is 100% JavaScript/TypeScript based, but the big difference with the usual JS/TS stacks is that with the AHA Stack you don’t build an SPA where a JavaScript UI Library takes control of the rendering happening in the browser.

You use the browser as indented: it renders HTML and responds to events, triggering HTTP requests.

The benefit is reduced complexity, and a simpler mental model and workflow.

It’s not for everyone.

I think the intended “audience” of this stack is developers that use Astro already, love it, and want to explore ways of building apps with Astro that do not involved a frontend framework like React or similar, because it lets you add “reactivity” / client-server interaction to a site in a very straightforward way.

It’s been a year since I started talking about Astro + HTMX (and Alpine) and I think I hit a “wall”, after the initial traction / interest, in terms of who is likely to “get” this.

Most people enjoying HTMX are people that despise JavaScript, and claim HTMX is about avoiding writing JavaScript. They use Go or Python or another backend language and will never use Astro because it’s made in JavaScript (/ TypeScript).

Most people enjoying Astro are on the opposite side of the spectrum and will claim it’s impossible to create “the kind of interactive experiences they create with a frontend framework” with HTMX. Mostly they are developers that only used a frontend framework during their whole career and HTMX is such a paradigm shift to them that they’ll go back to React or Svelte or anything they know already, at the first roadblock.

Understandably. This is how people do things, this is where you’ll find more tutorials, courses, libraries, people to hire for.

I don’t blame any of those 2 groups, I think the AHA Stack is a little bit in the middle, and probably the people that will “get” it the most are people that like JavaScript/TypeScript (I do, a lot) but try to reduce the complexity modern frontend engineering requires, perhaps even as a way to have fun on side projects after they come home from their job as a frontend engineer using a heavy frontend framework.

At the end of the day, the AHA Stack describes an approach, viable for many scenarios.

Astro

Astro is the tool of choice of The AHA Stack because it is minimal yet full of interesting features.

It gives you the simple basic stuff that you use to build a modern website, like file-based routing, built-in markdown rendering, great templating language.

It’s JavaScript-based, so we end up using a single language, and language mental model, across the entire stack.

If you know another classic Web Application server like Rails, Django, Laravel, or like building your own in Go or whatever, you can use anything you want.

The only thing this tool needs is the ability to handle HTTP responses, communicate with a database if needed, and reply with HTTP partials.

But Astro is JavaScript-centric, makes it super easy to add a more advanced client-side framework on specific pages if you need it. It provides routing, an easy way to handle HTTP requests, and has a very nice templating language that helps us design the HTML using components (not “Web Components”, but components in the React / modern JS framework way: self contained units you can reference as a tag, like <Todos />).

Here is why I really enjoy Astro:

Read my post why I use Astro.

HTMX

htmx defines itself as an “extension of HTML”.

It brings a few brilliant ideas:

Those 3 ideas alone are genius.

Then, it allows us to easily overcome the “replace the entire screen” concept, and introduces “replace just this part of the HTML with a snippet of HTML”.

It’s a brilliant little library, with no dependencies, that you install through a script tag. It’s backend-agnostic.

We use htmx to handle client-server HTTP communication once the page is loaded.

So for example the user clicks a link, and we load some data from the server, which we get back as HTML, and we add it to the page dynamically.

And we do this in a way that’s declarative.

Not imperatively writing JavaScript to tell the page what to do, instead, we go up a level of abstraction, and declare what we want it to do.

Read Why I use HTMX.

Alpine.js

Alpine.js is a great little library to add “JavaScript sprinkles of interactivity”.

That’s what JavaScript was created for in the first place: add more life to HTML pages (rather than, for example, run in the browser an entire application framework used to render HTML).

Wait, isn’t Alpine like htmx? Alpine and htmx help us do different things. htmx focuses on the client/server interaction, handling requests and responses. Alpine is more concerned with the client-side state. For example tracking the value of an input box, or opening a modal, etc.

You could also just use “vanilla” JavaScript DOM APIs for this, but for some things, Alpine simplifies a lot our job.

Read Why I use Alpine.js.

How they combine

So, in addition to the Web Server we have 2 tools:

Those are rather recent tools (the first public release of Alpine.js is dated Nov 2019 and htmx 0.0.1 is May 2020), but I’ve seen them mentioned a lot in the past few years.

Both have 25k stars on GitHub (at the time of writing), which means they’re popular enough. And they’re well maintained and developed.

Both can be included easily with a script tag in the HTML, and have no dependencies whatsoever.

This heavily simplifies your app builds, and both being authored in plain JavaScript make sure nothing can break in the future (because JavaScript never introduces breaking changes in its releases, just like HTML and CSS).

It’s back to basics.

Those technologies are great, but they are not set in stone. You can change them per your preference and switch to different ones in the future. They’re just tools.

Not fundamentals.

You could pick a complex SPA framework that reinvents everything and forces you to learn patterns and concepts that are completely made-up.

Or, you can use the Web platform as it was intended.

Adding some thin layers on top of it, to add those last bits we need in order to match the user experience that people expect us to be able to deliver.

The end goal is to make our life easier, simpler, because using this stack you can learn the few concepts htmx and Alpine require in an afternoon, and then that’s it.

The mental model is super simple, much closer to the Web first principles, and it lets you focus on the actual application you’re building, rather than learning yet another overcomplicated framework that introduces its own set of principles and ideas.

Did this introduction inspire you?

Did it unlock the idea of building a test application with this stack in the future, to see how it looks like?

I hope so.

How we got here

For years and years we’ve been building Web Applications using good old server-rendered HTML. Using the Web Platform as it was intended. Links to navigate to other pages, forms to send data to the server, server processes data and returns a new page with updated data.

The Web, originally meant as a way to read things and click links to read other things, found itself in this sweet position where people wanted to use it for more.

People needed to build for more complex user interfaces.

So people created more technologies to make this possible.

One of the technologies that gained traction was AJAX. Introduced by Microsoft with IE, it made possible to build apps like Gmail and Google Maps, which now we think they’re normal but were ground-breaking at the time they were introduced.

AJAX meant we could send a request to a URL, using JavaScript, and get some data back. Using JavaScript, we could add this new data to the page. Awesome. It was a bit complex initially, but libraries like jQuery introduced nice APIs like $.ajax() or $.post() to work with this approach. Now I’m getting nostalgic.

But at some point JavaScript became so powerful, and computers got so fast, that another idea started to emerge: what if we create a JavaScript application that runs inside the browser, whose job is to render HTML that the browser displays to the user?

That’s how we ended up with stuff like React.

React wasn’t the first (among the first we can remember Backbone, Ember, Knockout..) but it ended up being the most successful.

So to recap, Web Applications went from a Client-Server architecture where the client was “dumb” and was only responsible for displaying the HTML sent by the server, to becoming “smart” and running entire applications within a window.

You still need a Web Server, who is responsible for sending data, typically in the JSON format, which is a JavaScript-like serialized data format that got hugely popular, and handling actions and data mutations, interfacing with a database for example.

Most of the websites you use are applications.

And some of those applications are very complex. For example I think about Figma, things like Photopea, Google Maps which we already mentioned, even video editors like Descript are entirely browser-based.

Those applications wouldn’t be possible without a TON of JavaScript running in the client. It’s just incredible what we can do.

But most apps I build, and you build, are not on this level.

You might have a login workflow, some forms, people enter data, visualize data, etc etc, but they’re not inherently complex.

They’re just CRUD apps.

And the modern SPA approach is overkill for them.

HTML-first

One of the core concepts of the AHA stack is “HTML first”.

The server responds to the initial request using a complete HTML page, DOCTYPE, <html><head><body> and all.

This can be HTML that’s being pregenerated and cached, or generated dynamically at request time, it doesn’t matter.

The browser gets this HTML and renders it. This is the browser’s job, and it’s very fast and efficient at this.

When the User Interface demands to be dynamically updated, for example if the user clicks a button to “load more” items in a list, the server can respond with an HTML partial, just some bits of HTML that the browser needs.

htmx takes care of adding this new HTML into the page.

This is the super simple mental model of the HTML-first concept.

It’s all just HTML.

And CSS, or course.

And some JavaScript sprinkles for client-side interactivity when needed.

But at the core, we ship HTML to the browser. Not some JSON or other data formats the browser needs to interpret.

Simplicity

I’m a simple man. I love simplicity.

And I hate when things get so complicated it hurts my mind.

Building Web Applications doesn’t need to be complicated, in most cases.

Unless you’re creating something really complicated by design.

Otherwise, introducing complexity is entirely your choice.

But developers like complexity. Introducing complexity even when it’s not needed. Maybe not needed yet, maybe to prepare an app for a future that will never exist.

Sometimes decisions are taken unconsciously, for fear of looking dumb:

Peer pressure is the ultimate evil. New developers are especially prone to thinking “seniors” will make fun of them for picking some tech stack that doesn’t look complex:

How many times I’ve heard “this tech stack will scale” for an app that ended up never seeing the light of the day because you abandoned it after working on it for 3 months and then you lost the initial enthusiasm?

Or, building a SaaS on top of an overly complicated stack only to be stuck with 2 paying customers at $4.99/m?

If only the app stack complexity was directly correlated with the number of customers we’d get, we’d all be millionaires. But instead, it has nothing to do with it.

People use our app because they need it. If you happen to build something people need, and you are able to find customers in a way that’s sustainable, you’ll have success.

This has nothing to do with the app complexity.

Actually, it’s the opposite.

Building an app with simple technology lets you ship faster.

Shipping faster means you can get the app faster to market, test more ideas, see what works and what doesn’t.

And if something gets traction, you can work on it more.

In the context of Web Applications, these days most Web Developers are inevitably pushed towards using a SPA framework like React, Vue, Svelte, or “enter your favorite client-side framework”.

For the past 11 years I’ve been all-in on the JavaScript SPA (Single Page Application) approach, as the vast majority of frontend developers.

Don’t get me wrong: I love using an client-side rendering library like React or Svelte. It’s an interesting challenge and you can do cool stuff with it.

I mean, that’s all I’ve been doing for a decade or so. If it wasn’t fun, or useful, I would be doing something else. I started with Ember, then got into React, Vue, used Svelte, tried a lot of alternatives too over time.

But lately, I’ve been trying a new, different approach, I found it was much much simpler than what the vast majority of people talk about out there, and I decided to write about it because you might find it cool.

Why?

I stopped all I was doing to build this website to tell you about this collection of technologies I found working great for me.

Why?

Because I believe we build our world.

And quoting Steve Jobs:

Everything around you that you call life was made up by people that were no smarter than you and you can change it, you can influence it, you can build your own things that other people can use.

I can sense when I don’t like using something. And with nothing forcing me to use it, be it an employer or a sponsor, I can do whatever I want.

And I want simplicity.

But keeping things simple is hard:

“Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better.” – Edsger W. Dijkstra

[August 4, 2021](https://twitter.com/flaviocopes/status/1422893172573736960?ref_src=twsrc%5Etfw)

Things I’m into right now:

- Less abstraction, more predictability
- Less serverless, more server
- Less complexity, more simplicity
- Less "edge", more one single location (you know if my app has zero users...)
- Less "autoscaling", more "I just need this much"

[October 21, 2023](https://twitter.com/flaviocopes/status/1715793063551832106?ref_src=twsrc%5Etfw)

I tweeted this 2 years ago and I’ve only seen this materialize very recently for me:

I believe we’re on the verge of a major shift in how we build apps, the tools are all going in the same direction.

More simplicity, less boilerplate, easier, faster, and less fluff.

And deployment platforms becoming easier, cheaper, and well integrated with tools [https://t.co/BTMyXS7imc](https://t.co/BTMyXS7imc)

[November 12, 2021](https://twitter.com/flaviocopes/status/1459232384168214528?ref_src=twsrc%5Etfw)

Will it work in the real world?

You’ll tell me.

https://twitter.com/dhh/status/1738199473095074132

Does it scale?

Does it matter?

Do you even need to scale, or are we just talking in abstract terms?

How much scale are we talking about? And which scale, scale the complexity of the app, or the user base?

Can you build Facebook with this?

Mmmm… I don’t know. I could even tell you maybe. It’s just a bunch of forms. Post this, reply to that.

Try this on an internal use app, something to interface with an internal db, or just something personal.

Compare the DX (how do you like using this stack) and the resulting UX (how good of a UI you can create with this stack), and see.

Technical scale is a backend problem. This stack scales as much as your backend can scale.

Could this scale to a bigger team? To a bigger project? To a much bigger user base?

Maybe.

It depends.

And you have fewer things that can break.

You don’t need to have too many things in your head to understand how it works.

Because the underlying model is simpler to understand. And we’re just using some utility tools upon a model that’s battle-tested.

Some things with htmx and Alpine are just easier, in most cases.

Seriously, I was into that boat until very recently, so I know what I’m criticizing.

You start asking questions like “What I’m going to do with my time?”

I can’t expect to implement debouncing by just adding delay:200ms in an attribute when…

Is this too simple? Because it seems too simple.

Why Astro?

First, note that you can use any backend framework you prefer.

But I have to suggest one, and I pick Astro because it’s my favorite tool ever.

If you’re unfamiliar with it, let me explain the basics.

I started using it 2 years ago, and over time I probably build 20 sites with it.

You can create both static sites or server-rendered sites, or hybrid (some pages static, some server-rendered)

This makes sure you only use the server resources you need, and you can deploy it on any major deployment platform, including serverless platforms.

It has a simple mental model.

By default, no JavaScript is shipped to the client. At all. Pages are generated on the server, at build time by default.

Then it just ships HTML.

Which makes it perfect for The AHA Stack.

It provides a great JSX-like templating language that’s just perfect in my opinion (I consider JSX the best feature introduced by React). But unlike JSX in React, Astro renders on the server, either at build time or request time (SSG or SSR).

And using this templating language we can design UIs on the server using JS-based components, like you are used to in the frontend, and use the JavaScipt import syntax to use them (game changer feature).

If you want, you can then add any kind of frontend framework you want, in the so-called “island architecture”. Basically you can add a React component, or Svelte component, anything you want, inside a page. You don’t have “React runs the site” but “the site runs some React thing on this page”. Which is great when you need more interactivity that The AHA Stack default tooling allows.

Letting us build a fast Multi Page Application.

You can create API routes easily.

And with server-rendering you can process form requests using the Web platform’s FormData, Request, Response. Process cookies, connect to a DB, whatever.

You can do what you want, basically.

Then, most sites are about content, and Astro’s content story is unparalleled. Markdown support out of the box, content collections for content-heavy sites, image processing, and much more.

It has sane defaults.

It is growing in popularity, but stays true to its principles.

The team building it is amazing.

But then again, if you prefer, bring your own backend here.

No problem.

What about mobile apps?

The AHA Stack is perfect for Web experiences.

If you also have a mobile app, a TV app, a fridge app, whatever, those apps are likely to consume a JSON API, not HTML.

Create a separate JSON API that directly serves data from the database.

You can have the same primitives in the backend, and generate HTML for the Web, and JSON for the mobile app.

“The existence of a hypermedia API in no way means that you can’t also have a Data API”

Read more:

https://hypermedia.systems/json-data-apis/

https://htmx.org/essays/why-tend-not-to-use-content-negotiation/

Is this just AJAX?

I’ve seen this kind of thing on Twitter referring to htmx:

I would encourage looking at it with a fresh mind, and trying it first, instead of saying “it’s just AJAX”.

If by AJAX we mean “communication between client and server outside of a full-page request”, then yes htmx is AJAX. Just like when you use fetch() in client-side JS or Axios or react-query or any data fetching library.

Other than that, there’s nothing that resembles old-school AJAX.

With old-school AJAX, there was TON of JavaScript involved.

Try reading one of those 2006-2007 books about AJAX and you’ll see.

Between starting requests, and processing the response, it was a lot.

htmx is a declarative approach at data fetching and in general client-server HTTP communication, and also, it’s a declarative approach at defining what happens after the response.

It’s an HTML extension, really.

It offers an incredible layer of abstraction and it can do a lot of work for us.

And this makes a world of difference.

Like using p { color: red } in CSS, or using the CSS Paint API to achieve the same effect.

Even ChatGPT doesn’t recommend this:

Comparison with a “JS SPA” app

Let’s compare the AHA stack to the typical SPA approach, for example an app built using React:

Comparison with a “Web 1.0” app

Let’s compare the AHA stack to the traditional server-rendered HTML approach, think for example built using Django or Rails or Laravel, that does not use any client-side JavaScript to add interactivity:

Is this like Hotwire or Livewire?

Similar ideas, yes, “HTML over the wire”, but different tech stacks.

Livewire is Laravel based.

Hotwire is mostly Rails based (although it can be used without Rails, but I guess it’s easier if you use Rails).

Elixir has something similar too with Phoenix LiveView.

The AHA stack is a full-stack JavaScript concept.

You do all in JavaScript, as Astro is a (backend) JavaScript framework

Although the “A” of Astro can be swapped.

If you prefer coding in Python, use Django instead of Astro. Use Go, use anything. Use Rust or OCaml or anything. The important thing is that the server renders some HTML, and it’s fun for you to use. That’s the only requirement.

Even the other parts can be swapped.

Don’t like htmx? Use Alpine AJAX or Unpoly or Datastar.

Don’t like Alpine.js? Use any other client-side library for declarative-style “sprinkes of interactivity” like petite-vue.

Labeling “AHA” for the specific set of Astro, htmx and Alpine helps being more specific with how-to guides.

A little example

Let me do a very simple example involving Astro and htmx. This example does not use Alpine, but that is always an option when we want more client-side interactivity.

I want to sell you Astro and htmx first.

We’re going to have a page with 2 buttons, one to increment a counter, another to decrement the count.

See this thing in action at https://aha-test-flavio.fly.dev on a single fly.io server running in Virginia (info for network latency metrics).

I think this will demonstrate how easy this stack can be.

Install Astro

Terminal window

npm create astro@latest

Run the site and open it in VS Code

Terminal window

cd <project>code .npm run dev

Now create src/pages/index.astro

Write some server-side code to initialize a super simple data storage in a file called /tmp/count.txt, if the file does not exist, and we read the content of that file into a count variable that we add to the HTML (credits to theprimeagen for this clever idea):

---
import fs from 'node:fs'

try {
  fs.accessSync('/tmp/count.txt')
} catch {
  fs.writeFileSync('/tmp/count.txt', '0')
}

const count = fs.readFileSync('/tmp/count.txt', 'utf-8')
---

<html lang='en'>
  <head>
    <meta charset='utf-8' />
    <link rel='icon' type='image/svg+xml' href='/favicon.svg' />
    <meta name='viewport' content='width=device-width' />
    <meta name='generator' content='{Astro.generator}' />
    <title>Astro</title>
  </head>
  <body>
    <h1>Count: {count}</h1>
  </body>
</html>

Result in the browser so far:

In Astro the part between --- at the top is ran server-side, and the part below is the HTML returned to the client.

You could hook a database or anything, but that’s just a simple thing we can do to get started without using any 3rd party library.

Let’s now install htmx.

Just add this <script> tag to the HTML returned by index.astro:

<script src="https://unpkg.com/htmx.org@2"></script>

htmx is installed.

Now we can create the buttons to increment or decrement the count:

<body>
  <h1>Count: {count}</h1>

  <button hx-post="/api/increment">Increment</button>
  <button hx-post="/api/decrement">Decrement</button>
</body>

When you click the Increment button, htmx will issue a POST request to /api/increment.

Create src/pages/api/increment.astro

---
import fs from 'node:fs'
export const partial = true

const count = +fs.readFileSync('/tmp/count.txt', 'utf-8').trim() + 1
fs.writeFileSync('/tmp/count.txt', count.toString())
---

{count}

export const partial = true tells Astro this returns a simple “HTML fragment”, not a full page.

Clicking a button will now return the new count inside the button, because htmx by default swaps the returned HTML into the innerHTML of the element that triggered the network request.

You can change the HTML to

<body>
  <h1>
    Count: <span id='count'>{count}</span>
  </h1>

  <button hx-post='/api/increment' hx-target='#count'>
    Increment
  </button>
  <button hx-post='/api/decrement' hx-target='#count'>
    Decrement
  </button>
</body>

and now the count value is updated dynamically.

Click the button, you’ll see the count increment correctly:

Notice we shipped HTML (in this case, we just returned a number, but it’s returned as text/html mime type, not in a different format like JSON for example) back to the client, and this HTML is swapped into the page in the place we want.

We also create the “API call” to decrement the count in src/pages/api/decrement.astro

---
import fs from 'node:fs'
export const partial = true

const count = +fs.readFileSync('/tmp/count.txt', 'utf-8').trim() - 1
fs.writeFileSync('/tmp/count.txt', count.toString())
---

{count}

For simplicity we’re duplicating the file access logic, but bear with me.

All the count updates are happening without a full page reload, without having to write any JavaScript ourselves, without a “SPA” framework.

In the network panel of your browser DevTools you can see all the requests that just return some bits of HTML.

Reloading the page shows you the current count. The state is all managed on the server.

Let me tell you about OOB (out-of-band) swaps in htmx, because this will make something click, I think.

It’s definitely opened my mind.

In the HTML returned from /api/decrement or /api/increment, instead of returning {count} you could return:

<span id='count' hx-swap-oob='true'>
  {count}
</span>

and you wouldn’t need to have hx-target='#count' on the buttons any more. The HTML generated on the server decides what to swap (note that in this case you need to add hx-swap='none' on the button to prevent the inner HTML of the button to be replaced with empty content).

The amazing thing is you can have multiple elements in your returned HTML with hx-swap-oob='true' replacing different parts of your application.

This was just a little example of using Astro to generate the HTML and htmx to drive client-to-server interactivity in a way you’d usually think you’d need a complex SPA framework, and a ton of JavaScript, but here we didn’t write a single line of client-side JavaScript (we did write JS on the backend to read/write the state to file, but this is another story).

I’ve been using this stack to build a much more complex app, with lots of screens and interacion and login and database, and the approach scales pretty well.

Can this work for your use case too? As they say, it depends. Try it for some small scale stuff and see for yourself. That was a simple example using just Astro + htmx. The AHA stack also involves Alpine. I don’t show it in this example, but what’s that for?

Alpine is for example, I want to show/hide a menu on mobile. I want to upload files via drag and drop. I want to have a bin icon over an image when I hover with mouse to delete it. I want to double click an input field to edit it. I want to close an overlay when I click outside of it, or when I press “esc”. Also modals, although you can do them in htmx very nicely too, so that’s borderline. Anything that involves network, htmx.

Things that are just frontend, Alpine suits better. You can also go full vanilla but Alpine is more declarative and local to where the action is.

The example from the Hypermedia Systems book

The Hypermedia Systems book is the essential book you need to read when starting out with htmx.

In the book you build a simple contacts management application using Flask + htmx.

I created a repo that follows the book using Astro + htmx instead, using PocketBase as the backend.

Use this repo as you read the book, otherwise it will not make much sense to you. Hopefully this will turn into a full tutorial soon.

The PocketBase collection contains 5 fields, firstlastphoneemail.

Follow along the book and use this repository’s commits as a reference:

https://github.com/flaviocopes/astromediasystems/

It’s not 100% implemented, a lot of stuff is missing (there’s a lot!) but you can get the idea of what it’s like to work with htmx and Astro to build Web Applications.

Right now you can see in action hx-boost, active search, confirmation dialogs, using DELETE HTTP method, using hx-push-urlhx-select, and pagination.

More examples

I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter

  • C Handbook
  • Command Line Handbook
  • CSS Handbook
  • Express Handbook
  • Git Cheat Sheet
  • Go Handbook
  • HTML Handbook
  • JS Handbook
  • Laravel Handbook
  • Next.js Handbook
  • Node.js Handbook
  • PHP Handbook
  • Python Handbook
  • React Handbook
  • SQL Handbook
  • Svelte Handbook
  • Swift Handbook

JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025

Bootcamp 2025

Join the waiting list