rust
53 lines · 8 steps
Multi-tenant DB routing in Axum extractors
A custom Axum extractor resolves the tenant from a request's subdomain and hands the handler the right database pool.
Explained by
highlit
1use axum::{
2 extract::{FromRequestParts, Host},
3 http::{request::Parts, StatusCode},
4};
5use sqlx::PgPool;
6use std::sync::Arc;
7
8#[derive(Clone)]
9pub struct TenantRegistry {
10 pools: Arc<dashmap::DashMap<String, PgPool>>,
11}
12
13impl TenantRegistry {
14 pub async fn pool_for(&self, slug: &str) -> Option<PgPool> {
15 self.pools.get(slug).map(|p| p.clone())
16 }
17}
18
19pub struct TenantDb {
20 pub slug: String,
21 pub pool: PgPool,
22}
23
24impl<S> FromRequestParts<S> for TenantDb
25where
26 TenantRegistry: FromRef<S>,
27 S: Send + Sync,
28{
29 type Rejection = (StatusCode, &'static str);
30
31 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
32 let Host(host) = Host::from_request_parts(parts, state)
33 .await
34 .map_err(|_| (StatusCode::BAD_REQUEST, "missing host header"))?;
35
36 let slug = host
37 .split('.')
38 .next()
39 .filter(|s| !s.is_empty() && *s != "www")
40 .ok_or((StatusCode::BAD_REQUEST, "could not resolve tenant subdomain"))?
41 .to_owned();
42
43 let registry = TenantRegistry::from_ref(state);
44 let pool = registry
45 .pool_for(&slug)
46 .await
47 .ok_or((StatusCode::NOT_FOUND, "unknown tenant"))?;
48
49 Ok(TenantDb { slug, pool })
50 }
51}
52
53use axum::extract::FromRef;
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Custom extractors let you turn cross-cutting request logic into a typed handler argument.
- 2FromRef decouples an extractor from the concrete app state, so it composes with any state that can produce a TenantRegistry.
- 3Mapping each failure mode to a distinct status code keeps rejection handling explicit and honest.
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
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
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/multi-tenant-db-routing-in-axum-extractors-explained-rust-373c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.