Using HTMX with Astro Partials

Learn how to make an HTMX API using Astro Partials.

Table of Contents

  1. Overview
  2. Project Prerequisites
  3. Let’s GET Started!
  4. Keep Me POSTed!
  5. Conclusion

Overview

This recipe will get you started using Astro’s page partials API and HTMX GET/POST requests with hx-include.

Project Prerequisites

Before starting with this guide, you will need an Astro project ready to go. I will be starting with the blank template provided by npm create astro. I will also use Tailwind and @tailwindcss/typography in this example to keep styles verbose.

Then, add the HTMX script to your Astro project. I will use the CDN for the sake of this recipe, but you can choose your preferred method!

src/pages/index.astro
---
---
<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>
<script
src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
</head>
<body>
<h1>Astro</h1>
</body>
</html>

Let’s GET Started!

Let’s model some sort of stateful application for HTMX. In this example, each endpoint will encode the information to replace itself with the next one. For example,

pages/
api/first.astro -> api/second.astro -> api/third.astro -> api/first.astro

First, add some logic to make a GET request to /api/first when the page loads. I will also add some Tailwind classes as I go on, but you can choose to ignore them and do something else entirely.

src/pages/index.astro
---
---
<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>
<script
src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
</head>
<body class="prose p-4">
<h1>Astro</h1>
<div hx-get="/api/first" hx-trigger="load" hx-swap="outerHTML"></div>
</body>
</html>

Then, set up your a.astro, b.astro, and c.astro endpoints in src/pages/api. Each of these endpoints should export const partial = true. This signals to the Astro compiler that this page should not serve more HTML than is present in the .astro file. You can read more about page partials here.

src/pages/api/first.astro
---
export const partial = true;
---
<div id="first">
<h1>Hello, I'm First!</h1>
<button
class="rounded bg-slate-200 px-2"
hx-get="/api/second"
hx-target="#first"
hx-swap="outerHTML">Goto Second</button
>
</div>
src/pages/api/second.astro
---
export const partial = true;
---
<div id="second">
<h1>Bonjour, je suis le deuxième !</h1>
<button
class="rounded bg-slate-200 px-2"
hx-get="/api/third"
hx-target="#second"
hx-swap="outerHTML">Goto Third</button
>
</div>
src/pages/api/third.astro
---
export const partial = true;
---
<div id="third">
<h1>Привіт, я третій!</h1>
<button
class="rounded bg-slate-200 px-2"
hx-get="/api/first"
hx-target="#third"
hx-swap="outerHTML">Goto First</button
>
</div>

Test your HTMX creation using npm run dev, and click away at your lovely new button!

This should get you started using HTMX and Astro together, but there is a lot more you can do with an endpoint in Astro.

Before we move on, you may be asking:

Trevor, Astro already supports using TypeScript to write API endpoints. Why are you writing this in a .astro file?

— You, my faithful reader

To that I say: Astro’s HTML rendering engine is extremely powerful! TypeScript (.ts) endpoints are particularly useful for data and image generation. However, a .astro file gives us the ability to render with ease! Need an Astro component? Heck, just throw it in there!

Keep Me POSTed!

There are a few ways to send user input to the server with HTMX. Since <form>s are pretty trivial in terms of syntax, even with HTMX, I will try something a little different.

HTMX’s hx-include attribute allows you to select an element from the DOM tree and include its value as part of the AJAX request. Using this attribute, you could functionally make a POST endpoint out of an Astro file, and send it a body consisting of data from inputs all around a page. The following example is a little contrived, but it should give you a clear picture on how to handle this kind of request body.

First, add another <div> in index.astro as well as some logic to get this started.

src/pages/index.astro
---
---
<html lang="en">
<head>
<!-- ... -->
</head>
<body class="prose p-4">
<h1>Astro</h1>
<div hx-get="/api/first" hx-trigger="load" hx-swap="outerHTML"></div>
<div class="mt-4">
<input
name="post-input-1"
class="post-input rounded border border-black bg-slate-200 px-2"
id="post-input-1"
type="text"
/>
<input
name="post-input-2"
class="post-input rounded border border-black bg-slate-200 px-2"
id="post-input-2"
type="text"
/>
<button
class="rounded bg-slate-200 px-2"
hx-post="/api/postit"
hx-target="next #post-output"
hx-swap="beforeend"
hx-include=".post-input">Post It!</button
>
<ol id="post-output"></ol>
</div>
</body>
</html>

In the above example, I added a button which both POSTs to /api/postit and hx-includes any element with the class post-input. Pay attention to the input names, you will need those later!

Now, add some logic to handle a POST request in src/pages/api/postit.astro! This endpoint will return a response code (405, “Method not allowed”) for any request that is not a POST request. Don’t forget to export partial!

src/pages/api/postit.astro
---
export const partial = true;
if (Astro.request.method !== 'POST') {
return new Response('Method not allowed.', { status: 405 });
}
---

Then, add a way to handle the incoming FormData and display the output! You can get the values from the FormData using the input names I mentioned earlier.

src/pages/api/postit.astro
---
export const partial = true;
if (Astro.request.method !== 'POST') {
return new Response('Method not allowed.', { status: 405 });
}
const formData = await Astro.request.formData();
const data1 = formData.get('post-input-1').toString();
const data2 = formData.get('post-input-2').toString();
---
<li class="my-0 text-sky-600">{data1 + data2}</li>

This example will concatenate the text from each input and display that as a list item in the #post-output div below! Try it on for speed!

Conclusion

I hope that this example helps you get started on building AMAZING, BLAZINGLY FAST, STATEFUL HTMX web applications. At least, I hope you learned a little bit more about Astro page partials and HTMX attributes.

Thank you, dear Reader, for taking the time to try something new. Please stay alert for my next posts!