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

Walkthrough

Space play step click any line
Three takeaways
  1. 1Custom extractors let you turn cross-cutting request logic into a typed handler argument.
  2. 2FromRef decouples an extractor from the concrete app state, so it composes with any state that can produce a TenantRegistry.
  3. 3Mapping each failure mode to a distinct status code keeps rejection handling explicit and honest.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Multi-tenant DB routing in Axum extractors — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code