DEV Community

Syeed Talha
Syeed Talha

Posted on

Axum Middleware Explained for Absolute Beginners (With Full Code)

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.toml setup
  • 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"
Enter fullscreen mode Exit fullscreen mode

💻 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();
}
Enter fullscreen mode Exit fullscreen mode

🚀 What Happens When You Run This?

  1. You visit http://localhost:3000
  2. Middleware runs before
  3. Handler runs (waits 2 seconds)
  4. Middleware runs after
  5. Response is sent

Console output:

-> Before handler
<- After handler
Time: 2.00s
Enter fullscreen mode Exit fullscreen mode

🔥 Now Let’s Explain the Confusing Parts

This is the most important section.


1️⃣ req: Request<axum::body::Body>

req: Request<axum::body::Body>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2️⃣ next: middleware::Next

next: middleware::Next
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

What is this?

This sets up logging system.

Why do we need it?

Because this line alone does nothing:

info!("Server running...");
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

⚡ 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
Enter fullscreen mode Exit fullscreen mode

🏁 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)