rust
60 lines · 8 steps
Passing per-request context through Axum middleware
An Axum middleware layer builds a per-request context and hands it to a handler via request extensions.
Explained by
highlit
1use std::sync::Arc;
2use axum::{
3 body::Body,
4 extract::{Extension, State},
5 http::{Request, StatusCode},
6 middleware::Next,
7 response::{IntoResponse, Response},
8 Json,
9};
10use serde_json::json;
11use uuid::Uuid;
12
13#[derive(Clone)]
14struct AppState {
15 db: Arc<PgPool>,
16 feature_flags: Arc<FeatureFlags>,
17}
18
19#[derive(Clone)]
20struct RequestContext {
21 request_id: Uuid,
22 tenant: String,
23}
24
25async fn attach_context(mut req: Request<Body>, next: Next) -> Result<Response, StatusCode> {
26 let tenant = req
27 .headers()
28 .get("x-tenant-id")
29 .and_then(|v| v.to_str().ok())
30 .ok_or(StatusCode::BAD_REQUEST)?
31 .to_owned();
32
33 let ctx = RequestContext {
34 request_id: Uuid::new_v4(),
35 tenant,
36 };
37
38 req.extensions_mut().insert(ctx);
39 Ok(next.run(req).await)
40}
41
42async fn current_usage(
43 State(state): State<AppState>,
44 Extension(ctx): Extension<RequestContext>,
45) -> impl IntoResponse {
46 let quota = sqlx::query_scalar!(
47 "SELECT quota FROM tenants WHERE id = $1",
48 ctx.tenant
49 )
50 .fetch_one(&*state.db)
51 .await
52 .unwrap_or(0);
53
54 Json(json!({
55 "request_id": ctx.request_id,
56 "tenant": ctx.tenant,
57 "quota": quota,
58 "metering_enabled": state.feature_flags.is_enabled("metering"),
59 }))
60}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Request extensions let middleware attach typed data that downstream handlers pull out with the Extension extractor.
- 2Separating shared app state from per-request context keeps long-lived resources distinct from data that changes each call.
- 3Failing fast in middleware with a StatusCode error rejects malformed requests before any handler runs.
Related explainers
rust
use std::path::PathBuf; use clap::{Parser, Subcommand, ValueEnum};
Building a CLI with clap's derive macros
cli
derive-macros
argument-parsing
Intermediate
10 steps
go
package middleware import ( "context"
Correlation ID tracing middleware in Gin
middleware
request tracing
structured logging
Intermediate
7 steps
php
<?php declare(strict_types=1);
A single-action JSON user controller in PHP
invokable-class
json-api
validation
Intermediate
7 steps
rust
use std::collections::HashMap; use std::fmt; #[derive(Debug)]
Parsing a config file with typed errors in Rust
error-handling
enums
parsing
Intermediate
9 steps
rust
use chrono::{DateTime, Duration, FixedOffset, Utc}; #[derive(Debug)] pub struct EventWindow {
Parsing and measuring time windows in Rust
datetime
error-handling
pattern-matching
Intermediate
7 steps
typescript
import { DynamicModule, Module, Provider } from '@nestjs/common'; import Redis, { RedisOptions } from 'ioredis'; export const REDIS_CLIENT = Symbol('REDIS_CLIENT');
Building a dynamic Redis module in NestJS
dependency-injection
dynamic-modules
provider-tokens
Intermediate
8 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/passing-per-request-context-through-axum-middleware-explained-rust-192a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.