rust
51 lines · 7 steps
Feature-flag middleware in Axum
A shared, mutable flag set gates routes behind an async Axum middleware factory.
Explained by
highlit
1use std::{collections::HashSet, sync::Arc};
2
3use axum::{
4 body::Body,
5 extract::{Request, State},
6 http::StatusCode,
7 middleware::Next,
8 response::{IntoResponse, Response},
9};
10use tokio::sync::RwLock;
11
12#[derive(Clone, Default)]
13pub struct FeatureFlags {
14 enabled: Arc<RwLock<HashSet<String>>>,
15}
16
17impl FeatureFlags {
18 pub async fn is_enabled(&self, flag: &str) -> bool {
19 self.enabled.read().await.contains(flag)
20 }
21
22 pub async fn set(&self, flag: &str, on: bool) {
23 let mut guard = self.enabled.write().await;
24 if on {
25 guard.insert(flag.to_owned());
26 } else {
27 guard.remove(flag);
28 }
29 }
30}
31
32pub fn require_flag(
33 flag: &'static str,
34) -> impl Clone + Fn(State<FeatureFlags>, Request, Next) -> BoxedFuture {
35 move |State(flags): State<FeatureFlags>, req: Request, next: Next| {
36 Box::pin(async move {
37 if flags.is_enabled(flag).await {
38 next.run(req).await
39 } else {
40 (
41 StatusCode::NOT_FOUND,
42 format!("feature `{flag}` is not available"),
43 )
44 .into_response()
45 }
46 })
47 }
48}
49
50type BoxedFuture =
51 std::pin::Pin<Box<dyn std::future::Future<Output = Response<Body>> + Send>>;
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1An Arc<RwLock<...>> lets many handlers read shared state concurrently while still allowing exclusive updates.
- 2Returning a closure from a function lets you parameterize middleware — here, per-flag gating — while keeping one implementation.
- 3Axum middleware can either forward the request via next.run or short-circuit with its own response.
Related explainers
go
package middleware import ( "net/http"
Role-based access control middleware in Gin
middleware
authorization
closures
Intermediate
7 steps
python
from fastapi import FastAPI, Request, status from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse
Redacting sensitive fields in FastAPI errors
validation
error-handling
security
Intermediate
7 steps
go
func UserDashboardCache(rdb *redis.Client, ttl time.Duration) gin.HandlerFunc { return func(c *gin.Context) { claims, ok := c.Get("claims") if !ok {
Per-user response caching in Gin with Redis
middleware
caching
redis
Advanced
9 steps
rust
pub fn format_size(bytes: u64) -> String { const UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; if bytes < 1024 {
Formatting byte counts as human-readable sizes
bit-manipulation
formatting
unit-conversion
Intermediate
5 steps
go
package middleware import ( "compress/gzip"
How gzip HTTP middleware works in Go
middleware
compression
object-pooling
Intermediate
7 steps
rust
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; use base64::{DecodeError, Engine}; pub fn encode_standard(data: &[u8]) -> String {
Base64 encode and decode in Rust
base64
encoding
error-handling
Beginner
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/feature-flag-middleware-in-axum-explained-rust-a54d/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.