When it comes to Rust, although it's lauded as a language that is memory-safe, blazing fast and efficient, it's also known for having a compiler that will complain at you for everything (hence terms like "compiler-driven development" becoming a thing) and complex trait bounds that can getting it just right take time. In this article, we'll talk about tools that you can use to speed up your workflow: htmx with a templating engine and the web framework Axum (and of course, Shuttle!).
htmx is a JavaScript library designed to help you ship faster by allowing you to call endpoints from HTML elements instead of being required to do it manually which when combined with a HTML templating engine makes prototyping extremely quick - and we don't need to set anything up to do it, only being required as a minimum to use the CDN script (although we can also use it as an npm package). Shuttle allows you to move quickly by declaratively provisioning infrastructure like databases, key-value stores and more as main function parameters using the Shuttle runtime, letting you prototype new projects extremely quickly when used with htmx.
Using Shuttle
Shuttle is a service designed to make deployment as easy as possible, by provisioning a runtime that lets you add macros (or "annotations") as function arguments to your entrypoint function. The runtime will then do static code analysis to figure out what needs provisioning and will then spin up the relevant infrastructure required - for example, if you need a Postgres instance, you can just declare it in your fn main
arguments, use cargo shuttle run
to run locally and then it'll spin up a container for you using Docker without any further input on your part!
By using Shuttle, we can turn this:
to this:
Once you're done writing code, all you need to do is use cargo shuttle deploy
(with the --allow-dirty
flag if on a Git branch with uncommitted changes) and when it's done deploying, you should get a link to see your website! If you need to check your database connection string again, you can also use cargo shuttle resource list
to quickly check it.
Using HTMX
To start off with, we want a base.html
file that includes the head - which we'll add htmx to through the CDN.
We are also using Askama, which is a Rust HTML templating crate, with htmx. If you've ever used Python before, you might notice the syntax is quite similar to Jinja2 templates. Jinja2 is a web templating engine that describes itself as a "fast, expressive and extensible web templating engine" that's been around for quite a while and is a well known format given how many copies there are of libraries, inside and outside of Rust, that emulate Jinja2 syntax. Interested in learning more about Askama? Our new recent Shuttle Launchpad issue talks about it here.
Now let's make our index.html
file:
As you can see, we are extending the base.html
file and then declaring a block called content
- this is where we put our HTML that we want to add. As a simple example, we've added a form to add a new todo, as well as a placeholder div with an ID of "list".
For the next part, we'll want to have some of our HTML already written, so let's do that now:
As you can see, we've included a button with the todo
component that makes a DELETE request to the /todos/:id
route, but it targets the whole row and just deletes the row after the API call is done, which saves time having to manually delete the component from the DOM.
Making API calls
htmx allows you to make an API call without explicitly writing JavaScript for it, by allowing you use HTML attributes instead. When you make an API call with htmx, the library requires you to return HTML as a response - which is great for us because we can combine it with Askama templating so that we don't have to go through the hassle of trying to create a whole new element through pure JavaScript and then appending it to whatever element we choose. Let's take the form from above as an example:
The button makes a POST request to /todos
, triggered by clicking the button, targets the HTML element with an id of "todos-content" and places the resulting HTML as the last element within the target element.
As you can see, this speeds up development speed quite a lot! Not having to set up an opinionated framework and being able to quickly write things with a HTML templating engine makes things a lot quicker.
Streams and Server Sent Events with htmx
Being able to quickly mock up a CRUD app with htmx is great. However, Server Sent Events (SSE) and Websockets are also important functions for web applications and web services to work. Thankfully, htmx natively supports both. We can look at a basic mockup of receiving SSE with htmx by creating a new channel in our main function, then appending it as an Extension to our main function, as well as creating the necessary structs we need for the messages we're going to send through the channel:
Now we want to send a message from our create_todo
and delete_todo
handlers through our channel - we can add them by including our extension in the function signature, then after a successful SQL transaction we simply send a message to the channel:
Now we need to implement the stream handler, which we can do by creating a BroadcastStream
and then mapping our stream to Axum SSE events:
Then in our HTML, we will want to add an element that looks like this:
When you have the hx-sse
HTML element and then add or delete an item from the main page, you will then see a log of what ID the record had and what the action was. The item will get appended to the inner div without any other input from our side!
Finishing Up
Thanks for reading! I hope this guide to using htmx with Rust has helped you get a better insight into why it's currently rising in popularity at the moment. htmx is a great library that can be taken to new heights by using it in conjunction with HTML templating in Rust.
Did this article help you? Feel free to give us a star on GitHub!
You can find the article code here.