rust
59 lines · 8 steps
Flash messages via Axum middleware
A shared queue collects flash messages during a request, then serializes them into a cookie on the way out.
Explained by
highlit
1use axum::{
2 body::Body,
3 extract::Request,
4 http::{header::SET_COOKIE, HeaderValue},
5 middleware::Next,
6 response::Response,
7};
8use std::sync::{Arc, Mutex};
9
10#[derive(Clone, Default)]
11pub struct FlashQueue(Arc<Mutex<Vec<Flash>>>);
12
13#[derive(Clone)]
14pub struct Flash {
15 pub level: &'static str,
16 pub message: String,
17}
18
19impl FlashQueue {
20 pub fn push(&self, level: &'static str, message: impl Into<String>) {
21 self.0.lock().unwrap().push(Flash { level, message: message.into() });
22 }
23
24 fn drain(&self) -> Vec<Flash> {
25 std::mem::take(&mut *self.0.lock().unwrap())
26 }
27}
28
29pub async fn flash_middleware(mut req: Request, next: Next) -> Response {
30 let queue = FlashQueue::default();
31 req.extensions_mut().insert(queue.clone());
32
33 let mut res = next.run(req).await;
34
35 let messages = queue.drain();
36 if messages.is_empty() {
37 return res;
38 }
39
40 let encoded: Vec<String> = messages
41 .into_iter()
42 .map(|f| format!("{}:{}", f.level, urlencoding::encode(&f.message)))
43 .collect();
44
45 let cookie = format!(
46 "flash={}; Path=/; HttpOnly; SameSite=Lax; Max-Age=30",
47 urlencoding::encode(&encoded.join("|"))
48 );
49
50 if let Ok(value) = HeaderValue::from_str(&cookie) {
51 res.headers_mut().insert(SET_COOKIE, value);
52 }
53
54 res
55}
56
57async fn _bind() -> Body {
58 Body::empty()
59}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Request extensions let middleware hand a shared, mutable object to downstream handlers without global state.
- 2Wrapping state in Arc<Mutex<...>> makes it Clone-able and thread-safe while keeping a single underlying buffer.
- 3Draining collected state after the handler runs is a clean place to translate it into response side effects.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/flash-messages-via-axum-middleware-explained-rust-e5f5/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.