Crate axum_htmx

source ·
Expand description

§axum-htmx


axum-htmx is a small extension library providing extractors, responders, and request guards for htmx headers within axum.

§Table of Contents

§Getting Started

Run cargo add axum-htmx to add the library to your project.

§Extractors

All of the htmx request headers have a supported extractor. Extractors are infallible, meaning they will always succeed and never return an error. In the case where a header is not present, the extractor will return None or false dependant on the expected return type.

HeaderExtractorValue
HX-BoostedHxBoostedbool
HX-Current-URLHxCurrentUrlOption<axum::http::Uri>
HX-History-Restore-RequestHxHistoryRestoreRequestbool
HX-PromptHxPromptOption<String>
HX-RequestHxRequestbool
HX-TargetHxTargetOption<String>
HX-Trigger-NameHxTriggerNameOption<String>
HX-TriggerHxTriggerOption<String>

§Responders

All of the htmx response headers have a supported responder. A responder is a basic type that implements IntoResponseParts, allowing you to simply and safely apply the HX-* headers to any of your responses.

HeaderResponderValue
HX-LocationHxLocationaxum::http::Uri
HX-Push-UrlHxPushUrlaxum::http::Uri
HX-RedirectHxRedirectaxum::http::Uri
HX-RefreshHxRefreshbool
HX-Replace-UrlHxReplaceUrlaxum::http::Uri
HX-ReswapHxReswapaxum_htmx::responders::SwapOption
HX-RetargetHxRetargetString
HX-ReselectHxReselectString
HX-TriggerHxResponseTriggeraxum_htmx::serde::HxEvent
HX-Trigger-After-SettleHxResponseTriggeraxum_htmx::serde::HxEvent
HX-Trigger-After-SwapHxResponseTriggeraxum_htmx::serde::HxEvent

§Vary Responders

Also, there are corresponding cache-related headers, which you may want to add to GET responses, depending on the htmx headers.

For example, if your server renders the full HTML when the HX-Request header is missing or false, and it renders a fragment of that HTML when HX-Request: true, you need to add Vary: HX-Request. That causes the cache to be keyed based on a composite of the response URL and the HX-Request request header - rather than being based just on the response URL.

Refer to caching htmx docs section for details.

HeaderResponder
Vary: HX-RequestVaryHxRequest
Vary: HX-TargetVaryHxTarget
Vary: HX-TriggerVaryHxTrigger
Vary: HX-Trigger-NameVaryHxTriggerName

Look at the Auto Caching Management section for automatic Vary headers management.

§Auto Caching Management

Requires feature auto-vary.

Manual use of Vary Reponders adds fragility to the code, because of the need to manually control correspondence between used extractors and the responders.

We provide a middleware to address this issue by automatically adding Vary headers when corresponding extractors are used. For example, on extracting HxRequest, the middleware automatically adds Vary: hx-request header to the response.

Look at the usage example.

§Request Guards

Requires feature guards.

In addition to the extractors, there is also a route-wide layer request guard for the HX-Request header. This will redirect any requests without the header to “/” by default.

It should be noted that this is NOT a replacement for an auth guard. A user can trivially set the HX-Request header themselves. This is merely a convenience for preventing users from receiving partial responses without context. If you need to secure an endpoint you should be using a proper auth system.

§Examples

§Example: Extractors

In this example, we’ll look for the HX-Boosted header, which is set when applying the hx-boost attribute to an element. In our case, we’ll use it to determine what kind of response we send.

When is this useful? When using a templating engine, like minijinja, it is common to extend different templates from a _base.html template. However, htmx works by sending partial responses, so extending our _base.html would result in lots of extra data being sent over the wire.

If we wanted to swap between pages, we would need to support both full template responses and partial responses (as the page can be accessed directly or through a boosted anchor), so we look for the HX-Boosted header and extend from a _partial.html template instead.

use axum::response::IntoResponse;
use axum_htmx::HxBoosted;

async fn get_index(HxBoosted(boosted): HxBoosted) -> impl IntoResponse {
    if boosted {
        // Send a template extending from _partial.html
    } else {
        // Send a template extending from _base.html
    }
}

§Example: Responders

We can trigger any event being listened to by the DOM using an htmx trigger header.

use axum_htmx::HxResponseTrigger;

// When we load our page, we will trigger any event listeners for "my-event.
async fn index() -> (HxResponseTrigger, &'static str) {
    // Note: As HxResponseTrigger only implements `IntoResponseParts`, we must
    // return our trigger first here.
    (
        HxResponseTrigger::normal(["my-event", "second-event"]),
        "Hello, world!",
    )
}

htmx also allows arbitrary data to be sent along with the event, which we can use via the serde feature flag and the HxEvent type.

use serde_json::json;

// Note that we are using `HxResponseTrigger` from the `axum_htmx::serde` module
// instead of the root module.
use axum_htmx::{HxEvent, HxResponseTrigger};

async fn index() -> (HxResponseTrigger, &'static str) {
    let event = HxEvent::new_with_data(
        "my-event",
        // May be any object that implements `serde::Serialize`
        json!({"level": "info", "message": {
            "title": "Hello, world!",
            "body": "This is a test message.",
        }}),
    )
    .unwrap();

    // Note: As HxResponseTrigger only implements `IntoResponseParts`, we must
    // return our trigger first here.
    (HxResponseTrigger::normal([event]), "Hello, world!")
}

§Example: Router Guard

use axum::Router;
use axum_htmx::HxRequestGuardLayer;

fn router_one() -> Router {
    Router::new()
        // Redirects to "/" if the HX-Request header is not present
        .layer(HxRequestGuardLayer::default())
}

fn router_two() -> Router {
    Router::new()
        .layer(HxRequestGuardLayer::new("/redirect-to-this-route"))
}

§Feature Flags

FlagDefaultDescriptionDependencies
auto-varyDisabledA middleware to address htmx caching issuefutures, tokio, tower
guardsDisabledAdds request guard layers.tower, futures-core, pin-project-lite
serdeDisabledAdds serde support for the HxEvent and LocationOptionsserde, serde_json

§Contributing

Contributions are always welcome! If you have an idea for a feature or find a bug, let me know. PR’s are appreciated, but if it’s not a small change, please open an issue first so we’re all on the same page!

§Testing

cargo +nightly test --all-features

§License

axum-htmx is dual-licensed under either

at your option.

Modules§

  • auto_varyauto-vary
    A middleware to automatically add a Vary header when needed to address htmx caching issue
  • Axum extractors for htmx request headers.
  • guardguards
    Request guard for protecting a router against non-htmx requests.
  • HTTP headers used by htmx.
  • Axum responses for htmx response headers.

Structs§

Enums§

Constants§

  • Indicates that the request is via an element using hx-boost attribute.
  • The current URL of the browser.
  • true if the request is for history restoration after a miss in the local history cache.
  • Allows you to do a client-side redirect that does not do a full page reload.
  • The user response to an hx-prompt
  • Pushes a new URL onto the history stack.
  • Can be used to do a client-side redirect to a new location.
  • If set to true, the client will do a full refresh on the page.
  • Replaces the currelt URL in the location bar.
  • Always true.
  • A CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing hx-select on the triggering element
  • Allows you to specify how the response value will be swapped.
  • A CSS selector that update the target of the content update to a different element on the page.
  • The id of the target element, if it exists.
  • Can be set as a request or response header.
  • Allows you to trigger client-side events.
  • Allows you to trigger client-side events.
  • The name of the triggered element, if it exists.