howdy/flash
Flash messages: a short note that survives one redirect, such as “Settings saved” after a form post.
A form handler does its work, redirects with 303 so a refresh does
not post the form again, and puts the message on the redirect. The page
it lands on takes the messages, which clears them, and shows them:
import howdy/flash
let assert Ok(secret) = env.get("SECRET_KEY_BASE")
let flash = flash.new(secret:)
fn save(ctx: Context) {
// ... save the settings ...
controller.status(ctx, 303)
|> response.set_header("location", "/settings")
|> flash.put(flash, flash.Success, "Settings saved")
}
fn show(ctx: Context) {
use messages <- flash.take(ctx, flash)
page(messages)
}
howdy/ui/toast.flashed turns the messages into toasts.
The messages travel in a cookie, so they need no server-side storage and work without JavaScript. The cookie is signed with a key derived from the secret, so a message the page shows is always one the app wrote: a cookie planted by someone else, such as from a sibling subdomain, is ignored. Messages are not encrypted, so never put a secret in one. The text is shown as text, never as HTML.
Flash messages are for feedback on something the user just did. For telling someone about what others did, use notifications, which are kept until read.
Types
Where flash messages are kept and how they are signed. Build with new.
pub opaque type Flash
How the message should look.
pub type Level {
Info
Success
Danger
}
Constructors
-
Info -
Success -
Danger
Values
pub fn cookie_options(
flash: Flash,
options: cookie.Options,
) -> Flash
Use other cookie options, such as cookie.secure(False) while serving
plain HTTP on a host other than localhost.
pub fn new(secret secret: String) -> Flash
Flash messages signed with a key derived from secret. The secret must
be at least 32 bytes and should come from the environment, so it stays
the same across restarts and every node. The same secret can be shared
with other parts of the app, since the flash key is derived from it.
A shorter secret panics, as a configuration error.
The cookie is called howdy_flash, and uses cookie.defaults with a
ten minute lifetime, so a message nobody reads does not turn up much
later.
pub fn peek(
request: request.Request(body),
flash: Flash,
) -> List(Message)
The messages a request carries, without clearing them. For a layout
that wants to know whether there are any; a handler showing them should
use take.
pub fn put(
res: response.Response(body),
flash: Flash,
level: Level,
text: String,
) -> response.Response(body)
Add a message to the response, after any it already carries. Messages are kept in the order they were put, and the oldest are dropped if the cookie would grow too large.
pub fn take(
ctx: context.Context(guarded),
flash: Flash,
next: fn(List(Message)) -> response.Response(content.Content),
) -> response.Response(content.Content)
Read the messages put on the response that led here, and clear them so they show only once. The handler gets an empty list when there are none, or when the cookie was not signed with this flash’s key.
The cookie is cleared on the handler’s response, unless the handler put new messages on it itself.