axum-htmx/README.md

260 lines
10 KiB
Markdown
Raw Normal View History

2023-07-22 22:37:15 +02:00
# axum-htmx
2023-07-23 01:17:07 +02:00
<!-- markdownlint-disable -->
<div align="right">
<a href="https://crates.io/crates/axum-htmx">
<img src="https://img.shields.io/crates/v/axum-htmx?style=flat-square" alt="crates.io badge">
</a>
2023-07-23 02:06:25 +02:00
<a href="https://docs.rs/axum-htmx/latest/">
2023-07-23 01:17:07 +02:00
<img src="https://img.shields.io/docsrs/axum-htmx?style=flat-square" alt="docs.rs badge">
</a>
</div>
2023-07-23 01:21:58 +02:00
<br>
2023-07-23 01:17:07 +02:00
<!-- markdownlint-enable -->
2023-10-24 03:24:00 +02:00
`axum-htmx` is a small extension library providing extractors, responders, and
2023-12-03 21:55:56 +01:00
request guards for [htmx](https://htmx.org/) headers within
[axum](https://github.com/tokio-rs/axum).
2023-07-22 22:37:15 +02:00
2023-10-24 05:58:41 +02:00
## Table of Contents
- [axum-htmx](#axum-htmx)
- [Table of Contents](#table-of-contents)
- [Getting Started](#getting-started)
- [Extractors](#extractors)
- [Responders](#responders)
2024-06-16 08:43:22 +02:00
- [Auto Caching Management](#auto-caching-management)
2023-10-24 05:58:41 +02:00
- [Request Guards](#request-guards)
- [Examples](#examples)
- [Example: Extractors](#example-extractors)
- [Example: Responders](#example-responders)
- [Example: Router Guard](#example-router-guard)
- [Feature Flags](#feature-flags)
- [Contributing](#contributing)
- [License](#license)
2023-07-23 01:21:58 +02:00
## Getting Started
2023-07-22 22:37:15 +02:00
2023-12-03 21:55:56 +01:00
Run `cargo add axum-htmx` to add the library to your project.
2023-07-22 22:37:15 +02:00
2023-07-23 00:50:23 +02:00
## Extractors
All of the [htmx request headers](https://htmx.org/reference/#request_headers)
2023-08-09 02:21:36 +02:00
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.
2023-07-23 00:50:23 +02:00
2023-12-03 21:47:22 +01:00
| Header | Extractor | Value |
|------------------------------|---------------------------|---------------------------|
| `HX-Boosted` | `HxBoosted` | `bool` |
| `HX-Current-URL` | `HxCurrentUrl` | `Option<axum::http::Uri>` |
| `HX-History-Restore-Request` | `HxHistoryRestoreRequest` | `bool` |
| `HX-Prompt` | `HxPrompt` | `Option<String>` |
| `HX-Request` | `HxRequest` | `bool` |
| `HX-Target` | `HxTarget` | `Option<String>` |
| `HX-Trigger-Name` | `HxTriggerName` | `Option<String>` |
| `HX-Trigger` | `HxTrigger` | `Option<String>` |
2023-07-23 00:50:23 +02:00
2023-10-24 03:24:00 +02:00
## Responders
All of the [htmx response headers](https://htmx.org/reference/#response_headers)
2023-10-24 04:06:43 +02:00
have a supported responder. A responder is a basic type that implements
2023-10-24 03:57:00 +02:00
`IntoResponseParts`, allowing you to simply and safely apply the HX-* headers to
any of your responses.
2023-10-24 03:24:00 +02:00
2023-12-03 21:47:22 +01:00
| Header | Responder | Value |
|---------------------------|---------------------|-------------------------------------|
| `HX-Location` | `HxLocation` | `axum::http::Uri` |
| `HX-Push-Url` | `HxPushUrl` | `axum::http::Uri` |
| `HX-Redirect` | `HxRedirect` | `axum::http::Uri` |
| `HX-Refresh` | `HxRefresh` | `bool` |
| `HX-Replace-Url` | `HxReplaceUrl` | `axum::http::Uri` |
| `HX-Reswap` | `HxReswap` | `axum_htmx::responders::SwapOption` |
| `HX-Retarget` | `HxRetarget` | `String` |
| `HX-Reselect` | `HxReselect` | `String` |
| `HX-Trigger` | `HxResponseTrigger` | `axum_htmx::serde::HxEvent` |
| `HX-Trigger-After-Settle` | `HxResponseTrigger` | `axum_htmx::serde::HxEvent` |
| `HX-Trigger-After-Swap` | `HxResponseTrigger` | `axum_htmx::serde::HxEvent` |
2023-10-24 03:24:00 +02:00
2024-06-16 08:43:22 +02:00
### Vary Responders
2024-04-30 06:53:00 +02:00
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._
2024-06-16 08:43:22 +02:00
Refer to [caching htmx docs section][htmx-caching] for details.
2024-04-30 06:53:00 +02:00
| Header | Responder |
|-------------------------|---------------------|
| `Vary: HX-Request` | `VaryHxRequest` |
| `Vary: HX-Target` | `VaryHxTarget` |
| `Vary: HX-Trigger` | `VaryHxTrigger` |
| `Vary: HX-Trigger-Name` | `VaryHxTriggerName` |
2024-06-16 08:43:22 +02:00
Look at the [Auto Caching Management](#auto-caching-management) section for
automatic `Vary` headers management.
## Auto Caching Management
__Requires feature `auto-vary`.__
Manual use of [Vary Reponders](#vary-responders) adds fragility to the code,
because of the need to manually control correspondence between used extractors
and the responders.
We provide a [middleware](crate::AutoVaryLayer) 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][auto-vary-example].
2024-04-30 06:53:00 +02:00
2023-07-28 03:58:53 +02:00
## Request Guards
2024-06-16 08:43:22 +02:00
__Requires feature `guards`.__
2023-07-28 03:58:53 +02:00
In addition to the extractors, there is also a route-wide layer request guard
2023-07-29 23:05:05 +02:00
for the `HX-Request` header. This will redirect any requests without the header
to "/" by default.
2023-07-28 03:58:53 +02:00
2023-07-28 05:04:22 +02:00
_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
2023-07-28 05:21:31 +02:00
need to secure an endpoint you should be using a proper auth system._
2023-07-28 05:03:16 +02:00
2023-10-24 05:58:41 +02:00
## Examples
2023-10-24 03:55:47 +02:00
### Example: Extractors
2023-07-22 22:37:15 +02:00
2023-07-23 00:50:23 +02:00
In this example, we'll look for the `HX-Boosted` header, which is set when
applying the [hx-boost](https://htmx.org/attributes/hx-boost/) attribute to an
element. In our case, we'll use it to determine what kind of response we send.
2023-07-22 22:37:15 +02:00
2023-07-23 00:50:23 +02:00
When is this useful? When using a templating engine, like
[minijinja](https://github.com/mitsuhiko/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.
2023-07-22 22:37:15 +02:00
2023-07-23 00:50:23 +02:00
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.
2023-07-22 22:37:15 +02:00
2023-07-28 05:36:41 +02:00
```rust
2023-07-28 03:58:53 +02:00
use axum::response::IntoResponse;
use axum_htmx::HxBoosted;
2023-07-22 22:37:15 +02:00
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
}
}
```
2023-10-24 03:55:47 +02:00
### Example: Responders
2023-10-24 03:24:00 +02:00
2023-10-24 05:25:18 +02:00
We can trigger any event being listened to by the DOM using an [htmx
trigger](https://htmx.org/attributes/hx-trigger/) header.
2023-10-24 03:24:00 +02:00
```rust
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.
2023-10-24 03:24:00 +02:00
(
2023-12-03 21:33:27 +01:00
HxResponseTrigger::normal(["my-event", "second-event"]),
"Hello, world!",
2023-10-24 03:24:00 +02:00
)
}
```
2023-12-03 21:55:56 +01:00
`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.
2023-10-24 05:25:18 +02:00
```rust
2023-11-28 01:26:50 +01:00
use serde_json::json;
2023-10-24 05:25:18 +02:00
// Note that we are using `HxResponseTrigger` from the `axum_htmx::serde` module
// instead of the root module.
2023-12-01 20:15:10 +01:00
use axum_htmx::{HxEvent, HxResponseTrigger};
2023-10-24 05:25:18 +02:00
async fn index() -> (HxResponseTrigger, &'static str) {
2023-10-24 05:25:18 +02:00
let event = HxEvent::new_with_data(
"my-event",
2023-12-01 20:15:10 +01:00
// May be any object that implements `serde::Serialize`
2023-10-24 05:25:18 +02:00
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!")
2023-10-24 05:25:18 +02:00
}
```
2023-07-28 03:58:53 +02:00
### Example: Router Guard
2023-07-23 00:50:23 +02:00
2023-07-28 05:36:41 +02:00
```rust
2023-07-28 03:58:53 +02:00
use axum::Router;
use axum_htmx::HxRequestGuardLayer;
2023-07-29 23:10:02 +02:00
fn router_one() -> Router {
2023-07-28 03:58:53 +02:00
Router::new()
2023-07-29 23:06:49 +02:00
// Redirects to "/" if the HX-Request header is not present
2023-07-29 23:05:05 +02:00
.layer(HxRequestGuardLayer::default())
2023-07-28 03:58:53 +02:00
}
2023-07-29 23:06:49 +02:00
2023-07-29 23:10:02 +02:00
fn router_two() -> Router {
2023-07-29 23:06:49 +02:00
Router::new()
.layer(HxRequestGuardLayer::new("/redirect-to-this-route"))
}
2023-07-23 00:50:23 +02:00
```
2023-10-24 05:58:41 +02:00
## Feature Flags
2023-07-28 03:58:53 +02:00
<!-- markdownlint-disable -->
2024-06-16 08:43:22 +02:00
| Flag | Default | Description | Dependencies |
|-------------|----------|------------------------------------------------------------|---------------------------------------------|
| `auto-vary` | Disabled | A middleware to address [HTMx caching issue][htmx-caching] | `futures`, `tokio`, `tower` |
| `guards` | Disabled | Adds request guard layers. | `tower`, `futures-core`, `pin-project-lite` |
| `serde` | Disabled | Adds serde support for the `HxEvent` and `LocationOptions` | `serde`, `serde_json` |
2023-07-28 03:58:53 +02:00
<!-- markdownlint-enable -->
2023-07-28 04:02:16 +02:00
## 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!
2024-05-06 06:41:03 +02:00
### Testing
```sh
cargo +nightly test --all-features
```
2023-07-22 22:37:15 +02:00
## License
`axum-htmx` is dual-licensed under either
- **[MIT License](/LICENSE-MIT)**
- **[Apache License, Version 2.0](/LICENSE-APACHE)**
2023-07-22 22:37:15 +02:00
at your option.
2024-06-16 08:43:22 +02:00
[htmx-caching]: https://htmx.org/docs/#caching
[auto-vary-example]: https://github.com/robertwayne/axum-htmx/blob/main/examples/auto-vary.rs