Skip to content

Domain guards

A domain guard restricts a group of routes to a specific domain.
With domain guards you can serve multiple websites and/or APIs from the same Pavex application.

src/intro/blueprint.rs
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn bp() -> Blueprint {
    let mut bp = Blueprint::new();
    // Serve the website from the root domain...
    bp.domain("pavex.dev").nest(website_bp());
    // ...while reserving a subdomain for the REST API.
    bp.domain("api.pavex.dev").prefix("/v1").nest(api_bp());
    bp
}

fn website_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(GET, "/", f!(super::index));
    // Other web pages...
    bp
}

fn api_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(GET, "/users", f!(super::users));
    // Other API routes...
    bp
}

Static guards

The simplest case is a static domain, a domain guard that matches a single, predetermined domain:

src/static_/blueprint.rs
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.domain("pavex.dev").nest(pavex_bp());
    bp
}

fn pavex_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(GET, "/", f!(super::index));
    // Other routes...
    bp
}

It will only match requests to pavex.dev. It won't match, for example, requests to api.pavex.dev or www.pavex.dev.

Domain parameters

If your needs are more complex, you can make your domain guards dynamic:

src/dynamic/blueprint.rs
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.domain("{sub}.pavex.dev").nest(sub_bp());
    bp
}

fn sub_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(GET, "/", f!(super::index));
    // Other routes...
    bp
}

{sub} is a domain parameter.
It matches everything before .pavex.dev, up to the previous . or the beginning of the domain.
It matches, for example, api.pavex.dev and ui.pavex.dev. It won't match admin.api.pavex.dev or pavex.dev though!

You can have multiple domain parameters in the same domain guard, as long as they are separated by a .:

src/multi/blueprint.rs
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.domain("{user_id}.{tenant_id}.pavex.dev").nest(user_bp());
    bp
}

fn user_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(GET, "/", f!(super::index));
    // Other routes...
    bp
}

Catch-all parameters

Normal domain parameters are limited to a single DNS label—i.e. they stop at the previous . or at the end of the domain. You can use the * character to craft a catch-all domain parameter. It matches the rest of the domain, regardless of its contents:

src/catch_all/blueprint.rs
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.domain("{*any}.example.dev").nest(sub_bp());
    bp
}

fn sub_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(GET, "/", f!(super::handler));
    // Other routes...
    bp
}

{*any} matches everything before example.dev, even if it contains . separators.
{*any}.example.dev matches, for example, api.example.dev and ui.example.dev, but it also matches admin.api.example.dev.

To avoid ambiguity, you can have at most one catch-all parameter in a domain guard and it must be located at the very beginning of the domain.

Domain detection

The domain requested by the client is determined using the Host header. If the header is not present, none of your domain guards will match.

Security

The Host header can be easily spoofed by the client. You shouldn't rely on domain guards for auth or other security-sensitive checks.

Restrictions

Domain guards are an all-or-nothing deal.
Either you specify a domain guard for all routes in a blueprint, or you don't specify any at all.

We recommend specifying domain guards at the very top, clearly partitioning your routes according to the domain they should be served on, as shown in the very first example for this guide.

The only exception to this rule are fallbacks: you can register a top-level fallback that will be invoked when no domain guard matches.

src/fallback/blueprint.rs
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn bp() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.domain("pavex.dev").nest(website_bp());
    bp.domain("api.pavex.dev").prefix("/v1").nest(api_bp());
    // If no domain matches, serve a 404 page.
    bp.fallback(f!(super::unknown_domain));
    bp
}

Absolute form

Pavex doesn't make a distinction between absolute and relative domain names.
If there a single trailing . at the end of a domain name, it will be stripped. For example, Pavex treats pavex.dev and pavex.dev. as the same domain.