Vapor + HTMX

Hi. Have you tried using HTMX with Vapor? I find the concept of HTMX quite refreshing.

If you are new to HTMX, then here is the Fireship 100 seconds video: htmx in 100 seconds and here is the official intro by htmx.org itself:

  • Why should only <a> and <form> be able to make HTTP requests?
  • Why should only click & submit events trigger them?
  • Why should only GET & POST methods be available?
  • Why should you only be able to replace the entire screen?

By removing these arbitrary constraints, htmx completes HTML as a hypertext.

For various reasons, I decided to try it together with Vapor as a backend. Honestly, it is the first properly sized side project I am doing in Vapor.

As I was using both, I had to write quite a number of small helpers, and now I would like to share them with the wider community to help with adopting HTMX in your Vapor projects faster.

Meet VaporHX: https://github.com/RussBaz/VaporHX

In order to speed up the adoption of HTMX, I came up with 2 core concepts:

  • VaporHX easily adds HTMX capabilities to existing API endpoints by adding hx(template: String) extension method to all Content, HTTPStatus and Abort types. In case of Content protocol, it will inject your content into the specified template on HTMX/HTML request and serialise it to JSON otherwise.
  • The library automatically selects the response type (such as API, HTML or HTMX) based on the request Accept header value + its method. It will also automatically wrap HTMX fragments with a dynamically generated leaf template to return a proper HTML during a normal browser request (referred internally as page). Therefore, you do not need to create a separate template when you need the whole page to be returned instead of just an HTMX fragment.

Here is a basic example:

struct MyApi: Content {
    let name: String
}

app.get("api") { req in
    MyApi(name: "name").hx(template: "api") // Return type is 'HX<MyApi>'
}

Of course HTMX/HTML only methods are available as well:

app.get { req in
    try await req.htmx.render("index")
}

I am still working on the documentation, but the library itself is already functional enough for others to try.

Please feel free to try it and let me know how it works for you. Any feedback is welcome. This is my first public swift project, btw. Hope you will find it useful.

Thanks!

edit: Renamed the repository

9 Likes