A Tower layer for Axum that enables subdomain-based routing in Rust web applications.
- Subdomain Routing: Route requests based on the
Hostheader subdomain - Known Hosts Support: Configure known host suffixes for proper subdomain extraction
- Strict Mode: Optionally return 404 for unknown subdomains
- Host Source Control: Use
Hostonly or allow optionalX-Forwarded-Hostfallback - Axum Compatible: Seamlessly integrates with Axum's
Router - IP Address Handling: Properly handles IP addresses in host headers
Add this to your Cargo.toml:
[dependencies]
axum-subdomain-routing = "0.0.8"Here's a basic example of how to use the SubdomainLayer:
use axum::{Router, routing::get};
use axum_subdomain_routing::SubdomainLayer;
#[tokio::main]
async fn main() {
// Create your main router
let main_router = Router::new()
.route("/", get(|| async { "Welcome to the main site!" }));
// Create routers for subdomains
let api_router = Router::new()
.route("/", get(|| async { "API endpoint" }))
.route("/users", get(|| async { "Users API" }));
let admin_router = Router::new()
.route("/", get(|| async { "Admin panel" }));
// Apply the subdomain layer
let app = main_router.layer(
SubdomainLayer::new()
.register("api", api_router)
.register("admin", admin_router)
);
// The server will now route:
// - example.com/ -> main_router
// - api.example.com/ -> api_router
// - admin.example.com/ -> admin_router
// Run your Axum server as usual
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}If your application serves multiple domains, you can specify known host suffixes:
let layer = SubdomainLayer::new()
.known_hosts(vec!["example.com".to_string(), "example.org".to_string()])
.register("api", api_router);Enable strict mode to return 404 for unknown subdomains:
let layer = SubdomainLayer::new()
.strict(true)
.register("api", api_router);In strict mode, requests to unknown subdomains will return a 404 response instead of falling back to the main router. Missing or malformed effective host values also return 404 in strict mode.
By default, the layer only uses the Host header. You can opt into X-Forwarded-Host fallback:
use axum_subdomain_routing::{HostSource, SubdomainLayer};
let layer = SubdomainLayer::new()
.host_source(HostSource::XForwardedHostFallback)
.register("api", api_router);The main layer struct for subdomain routing.
new() -> SubdomainLayer: Creates a newSubdomainLayerinstance.register<S: ToString>(self, subdomain: S, router: Router) -> Self: Registers a router for the specified subdomain.strict(self, strict: bool) -> Self: Enables or disables strict subdomain checking.known_hosts(self, hosts: Vec<String>) -> Self: Sets the list of known host suffixes.host_source(self, host_source: HostSource) -> Self: Configures host resolution strategy.
The service created by the layer. You typically won't interact with this directly.
The layer extracts the subdomain from the Host header of incoming requests:
- It checks against configured known hosts to strip domain suffixes
- Falls back to automatic TLD detection for common top-level domains
- Handles IP addresses by replacing dots with underscores
- Routes to the appropriate registered router or falls back to the main router
Contributions are welcome! Please feel free to submit a Pull Request!
This project is licensed under the MIT License - see the repository for details.