Middleware in Axum often looks scary—not because it’s hard, but because the code syntax feels unfamiliar.
This guide explains everything in a very simple, beginner-friendly way, including:
- Full working code
-
Cargo.tomlsetup - Clear explanation of the confusing parts
🧠 What is Middleware?
Middleware is just a function that runs:
- before your handler
- after your handler
Think of it like a wrapper around your route.
📦 Step 1: Cargo.toml Setup
Create a new Rust project and add this:
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
💻 Step 2: Full Working Code
use axum::{
routing::get,
Router,
middleware,
extract::Request,
response::Response,
};
use tracing::info;
use std::time::Instant;
use tokio::time::{sleep, Duration};
async fn handler() -> &'static str {
sleep(Duration::from_secs(2)).await;
"Hello world from Axum"
}
async fn my_middleware(
req: Request<axum::body::Body>,
next: middleware::Next,
) -> Response {
println!("-> Before handler");
let start = Instant::now();
let res = next.run(req).await;
println!("<- After handler");
println!("Time: {:?}", start.elapsed());
res
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(my_middleware));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
info!("Server running on http://0.0.0.0:3000");
axum::serve(listener, app).await.unwrap();
}
🚀 What Happens When You Run This?
- You visit
http://localhost:3000 - Middleware runs before
- Handler runs (waits 2 seconds)
- Middleware runs after
- Response is sent
Console output:
-> Before handler
<- After handler
Time: 2.00s
🔥 Now Let’s Explain the Confusing Parts
This is the most important section.
1️⃣ req: Request<axum::body::Body>
req: Request<axum::body::Body>
What is this?
This is the incoming HTTP request.
It contains:
- URL (
/) - headers
- body (data sent by client)
Why is it written like this?
Rust is very strict about types, so it says:
-
Request<T>→ request with some body type -
axum::body::Body→ the actual body format
👉 Simple meaning:
“This is the full request coming from the client”
You can just think:
req = user request
2️⃣ next: middleware::Next
next: middleware::Next
What is this?
This represents the next step in the chain.
That could be:
- your handler
- or another middleware
Simple meaning:
“What should happen after this middleware?”
3️⃣ The MOST IMPORTANT Line
let res = next.run(req).await;
What is happening here?
This line says:
“Send the request forward and wait for the response”
Break it down:
| Part | Meaning |
|---|---|
next.run(...) |
call the next step |
req |
pass the request |
.await |
wait for result |
res |
store the response |
Why is this REQUIRED?
Without this line:
- the handler will never run
- the request will stop inside middleware
👉 Middleware must always decide:
- forward request ❓
- or block it ❌
4️⃣ tracing_subscriber::fmt::init();
tracing_subscriber::fmt::init();
What is this?
This sets up logging system.
Why do we need it?
Because this line alone does nothing:
info!("Server running...");
Rust logging works like this:
-
tracing→ creates logs -
tracing-subscriber→ displays logs
Without init():
- logs are silently ignored ❌
With init():
- logs are printed to console ✅
5️⃣ info!
info!("Server running on http://0.0.0.0:3000");
What is this?
This is a log message, like println! but better.
Why use it instead of println!?
Because it supports:
- log levels (
info,debug,error) - filtering
- structured logging
👉 Example:
RUST_LOG=debug cargo run
⚡ Quick Comparison
| Feature | println! | info! |
|---|---|---|
| Works instantly | ✅ | ❌ (needs init) |
| Log levels | ❌ | ✅ |
| Production use | ❌ | ✅ |
🎯 Final Mental Model
Whenever you see middleware, think:
1. Receive request
2. Do something BEFORE
3. Call next.run(req)
4. Do something AFTER
5. Return response
🏁 Summary
-
req→ the incoming request -
next→ what happens next -
next.run(req).await→ pass control forward -
tracing_subscriber::fmt::init()→ enable logging -
info!→ structured logging
Middleware is not complex — it just looks complex.
Once you understand the flow, everything becomes simple.
If you want to go further, next steps could be:
- Authentication middleware
- Logging every request
- Error handling middleware
Just continue building — that’s the best way to learn 🚀
Top comments (0)